Skip to content

Instantly share code, notes, and snippets.

@Ricket
Created December 5, 2022 05:53
Show Gist options
  • Save Ricket/a6b5a83240a448e27126de2db37052e5 to your computer and use it in GitHub Desktop.
Save Ricket/a6b5a83240a448e27126de2db37052e5 to your computer and use it in GitHub Desktop.
Minecraft backups: prune the backups to just one file per play session (i.e. find groups of consecutive backups and delete all but the newest)
from datetime import datetime, timedelta
from dateutil import tz
from functools import reduce
import os
from pprint import pprint
import re
import sys
if sys.version_info < (3,7):
sys.exit('Python 3.7 guarantees ordered dicts, which this script relies on')
dir = r'Z:\srv\minecraft\backups\Enigmatica6Expert\backups'
min_time_between_backups = timedelta(hours=3)
min_time_ago_to_cull = timedelta(days=14)
#date_regex = re.compile(r'^Backup--world--(?P<year>[0-9]+)-(?P<month>[0-9]+)-(?P<day>[0-9]+)--(?P<hour>[0-9]+)-(?P<minute>[0-9]+).zip$')
date_regex = re.compile(r'^(?P<year>[0-9]+)-(?P<month>[0-9]+)-(?P<day>[0-9]+)-(?P<hour>[0-9]+)-(?P<minute>[0-9]+)-(?P<second>[0-9]+).zip$')
names = os.listdir(dir)
names.sort() # it seems to already be sorted, but just in case...
def parse_date(filename):
match = date_regex.match(filename)
if match is None:
raise ValueError(f'Invalid filename: {filename}')
return datetime(int(match['year']), int(match['month']), int(match['day']), int(match['hour']), int(match['minute']), tzinfo=tz.tzutc())
dates = {name: parse_date(name) for name in names}
def sufficiently_spaced(datetime1, datetime2):
delta = abs(datetime1 - datetime2)
return delta >= min_time_between_backups
now = datetime.now(tz=tz.tzlocal())
def add_preserve_entry(map, entry):
name, date = entry
# if first record, default to preserving it
if len(map) == 0:
map[name] = True
return map
# get the previous entry
prevname = list(map.keys())[-1]
prevdate = dates[prevname]
# preserve the current entry
map[name] = True
# only delete it if it's more than min_time_ago
old_enough = (now - prevdate) > min_time_ago_to_cull
# if the previous one was within a short delta, don't keep it
if old_enough and not sufficiently_spaced(prevdate, date):
map[prevname] = False
return map
preserve = reduce(add_preserve_entry, dates.items(), {})
#pprint(preserve)
#exit(0)
files_to_delete = [key for key, value in preserve.items() if value is False]
#pprint(files_to_delete)
files_to_keep = [key for key, value in preserve.items() if value is True]
#print("Keeping files:")
#pprint(files_to_keep)
sizes = {file: os.path.getsize(f'{dir}\{file}') for file in files_to_delete}
total_bytes = sum(sizes.values())
bytes_so_far = 0
for file in files_to_delete:
os.remove(f'{dir}\{file}')
bytes_so_far += sizes[file]
progress = bytes_so_far / total_bytes
print(f'{file} {progress:.0%}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment