11import argparse
22from collections import OrderedDict
3- from datetime import datetime , timezone , timedelta
3+ from datetime import datetime
44import logging
55from operator import attrgetter
66import os
99from ..archive import Archive
1010from ..cache import Cache
1111from ..constants import * # NOQA
12- from ..helpers import ArchiveFormatter , interval , sig_int , ProgressIndicatorPercent , CommandError , Error
12+ from ..helpers import (
13+ ArchiveFormatter ,
14+ interval ,
15+ int_or_interval ,
16+ sig_int ,
17+ ProgressIndicatorPercent ,
18+ CommandError ,
19+ Error ,
20+ )
1321from ..helpers import archivename_validator
1422from ..manifest import Manifest
1523
1826logger = create_logger ()
1927
2028
21- def prune_within (archives , seconds , kept_because ):
22- target = datetime .now (timezone .utc ) - timedelta (seconds = seconds )
23- kept_counter = 0
24- result = []
25- for a in archives :
26- if a .ts > target :
27- kept_counter += 1
28- kept_because [a .id ] = ("within" , kept_counter )
29- result .append (a )
30- return result
29+ def unique_func ():
30+ counter = 0
31+
32+ def inner (a ):
33+ nonlocal counter
34+ counter += 1
35+ return counter
36+
37+ return inner
3138
3239
3340def default_period_func (pattern ):
@@ -77,6 +84,9 @@ def quarterly_3monthly_period_func(a):
7784
7885PRUNING_PATTERNS = OrderedDict (
7986 [
87+ ("within" , unique_func ()),
88+ ("last" , unique_func ()),
89+ ("keep" , unique_func ()),
8090 ("secondly" , default_period_func ("%Y-%m-%d %H:%M:%S" )),
8191 ("minutely" , default_period_func ("%Y-%m-%d %H:%M" )),
8292 ("hourly" , default_period_func ("%Y-%m-%d %H" )),
@@ -90,7 +100,12 @@ def quarterly_3monthly_period_func(a):
90100)
91101
92102
93- def prune_split (archives , rule , n , kept_because = None ):
103+ def prune_split (archives , rule , n_or_interval , kept_because = None ):
104+ if isinstance (n_or_interval , int ):
105+ n , interval = n_or_interval , None
106+ else :
107+ n , interval = None , n_or_interval
108+
94109 last = None
95110 keep = []
96111 period_func = PRUNING_PATTERNS [rule ]
@@ -104,24 +119,30 @@ def prune_split(archives, rule, n, kept_because=None):
104119 period = period_func (a )
105120 if period != last :
106121 last = period
107- if a .id not in kept_because :
122+ if a .id not in kept_because and ( interval is None or a . ts >= datetime . now (). astimezone () - interval ) :
108123 keep .append (a )
109124 kept_because [a .id ] = (rule , len (keep ))
110125 if len (keep ) == n :
111126 break
127+
112128 # Keep oldest archive if we didn't reach the target retention count
113- if a is not None and len (keep ) < n and a .id not in kept_because :
129+ if a is not None and ( n is not None and len (keep ) < n ) and a .id not in kept_because :
114130 keep .append (a )
115131 kept_because [a .id ] = (rule + "[oldest]" , len (keep ))
132+
116133 return keep
117134
118135
119136class PruneMixIn :
120137 @with_repository (compatibility = (Manifest .Operation .DELETE ,))
121138 def do_prune (self , args , repository , manifest ):
122139 """Prune repository archives according to specified rules"""
123- if not any (
124- (
140+ if all (
141+ e is None
142+ for e in (
143+ args .keep ,
144+ args .within ,
145+ args .last ,
125146 args .secondly ,
126147 args .minutely ,
127148 args .hourly ,
@@ -131,11 +152,10 @@ def do_prune(self, args, repository, manifest):
131152 args .quarterly_13weekly ,
132153 args .quarterly_3monthly ,
133154 args .yearly ,
134- args .within ,
135155 )
136156 ):
137157 raise CommandError (
138- 'At least one of the "keep-within", "keep-last", '
158+ 'At least one of the "keep", "keep -within", "keep-last", '
139159 '"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
140160 '"keep-weekly", "keep-monthly", "keep-13weekly", "keep-3monthly", '
141161 'or "keep-yearly" settings must be specified.'
@@ -159,15 +179,11 @@ def do_prune(self, args, repository, manifest):
159179 # (<rulename>, <how many archives were kept by this rule so far >)
160180 kept_because = {}
161181
162- # find archives which need to be kept because of the keep-within rule
163- if args .within :
164- keep += prune_within (archives , args .within , kept_because )
165-
166182 # find archives which need to be kept because of the various time period rules
167183 for rule in PRUNING_PATTERNS .keys ():
168- num = getattr (args , rule , None )
169- if num is not None :
170- keep += prune_split (archives , rule , num , kept_because )
184+ num_or_interval = getattr (args , rule , None )
185+ if num_or_interval is not None :
186+ keep += prune_split (archives , rule , num_or_interval , kept_because )
171187
172188 to_delete = set (archives ) - set (keep )
173189 with Cache (repository , manifest , iec = args .iec ) as cache :
@@ -310,81 +326,90 @@ def build_parser_prune(self, subparsers, common_parser, mid_common_parser):
310326 help = "keep all archives within this time interval" ,
311327 )
312328 subparser .add_argument (
313- "--keep-last" ,
329+ "--keep-last" , dest = "last" , type = int , action = Highlander , help = "number of archives to keep"
330+ )
331+ subparser .add_argument (
332+ "--keep" ,
333+ dest = "keep" ,
334+ type = int_or_interval ,
335+ action = Highlander ,
336+ help = "number or time interval of archives to keep" ,
337+ )
338+ subparser .add_argument (
314339 "--keep-secondly" ,
315340 dest = "secondly" ,
316- type = int ,
341+ type = int_or_interval ,
317342 default = 0 ,
318343 action = Highlander ,
319- help = "number of secondly archives to keep" ,
344+ help = "number or time interval of secondly archives to keep" ,
320345 )
321346 subparser .add_argument (
322347 "--keep-minutely" ,
323348 dest = "minutely" ,
324- type = int ,
349+ type = int_or_interval ,
325350 default = 0 ,
326351 action = Highlander ,
327- help = "number of minutely archives to keep" ,
352+ help = "number or time interval of minutely archives to keep" ,
328353 )
329354 subparser .add_argument (
330355 "-H" ,
331356 "--keep-hourly" ,
332357 dest = "hourly" ,
333- type = int ,
358+ type = int_or_interval ,
334359 default = 0 ,
335360 action = Highlander ,
336- help = "number of hourly archives to keep" ,
361+ help = "number or time interval of hourly archives to keep" ,
337362 )
338363 subparser .add_argument (
339364 "-d" ,
340365 "--keep-daily" ,
341366 dest = "daily" ,
342- type = int ,
367+ type = int_or_interval ,
343368 default = 0 ,
344369 action = Highlander ,
345- help = "number of daily archives to keep" ,
370+ help = "number or time interval of daily archives to keep" ,
346371 )
347372 subparser .add_argument (
348373 "-w" ,
349374 "--keep-weekly" ,
350375 dest = "weekly" ,
351- type = int ,
376+ type = int_or_interval ,
352377 default = 0 ,
353378 action = Highlander ,
354- help = "number of weekly archives to keep" ,
379+ help = "number or time interval of weekly archives to keep" ,
355380 )
356381 subparser .add_argument (
357382 "-m" ,
358383 "--keep-monthly" ,
359384 dest = "monthly" ,
360- type = int ,
385+ type = int_or_interval ,
361386 default = 0 ,
362387 action = Highlander ,
363- help = "number of monthly archives to keep" ,
388+ help = "number or time interval of monthly archives to keep" ,
364389 )
365390 quarterly_group = subparser .add_mutually_exclusive_group ()
366391 quarterly_group .add_argument (
367392 "--keep-13weekly" ,
368393 dest = "quarterly_13weekly" ,
369- type = int ,
394+ type = int_or_interval ,
370395 default = 0 ,
371- help = "number of quarterly archives to keep (13 week strategy)" ,
396+ help = "number or time interval of quarterly archives to keep (13 week strategy)" ,
372397 )
373398 quarterly_group .add_argument (
374399 "--keep-3monthly" ,
375400 dest = "quarterly_3monthly" ,
376- type = int ,
401+ type = int_or_interval ,
377402 default = 0 ,
378- help = "number of quarterly archives to keep (3 month strategy)" ,
403+ help = "number or time interval of quarterly archives to keep (3 month strategy)" ,
379404 )
380405 subparser .add_argument (
381406 "-y" ,
382407 "--keep-yearly" ,
383408 dest = "yearly" ,
384- type = int ,
409+ type = int_or_interval ,
385410 default = 0 ,
386411 action = Highlander ,
387- help = "number of yearly archives to keep" ,
412+ help = "number or time interval of yearly archives to keep" ,
388413 )
389414 define_archive_filters_group (subparser , sort_by = False , first_last = False )
390415 subparser .add_argument (
0 commit comments