Release v0.1.29: media previews, clean composer, regional profiles and cloud retention
This commit is contained in:
+37
-1
@@ -1,5 +1,6 @@
|
||||
"""Online SQLite snapshots with optional copy to a pre-mounted network directory."""
|
||||
import datetime as dt
|
||||
import calendar
|
||||
from contextlib import closing
|
||||
import hashlib
|
||||
import os
|
||||
@@ -111,6 +112,35 @@ class Backups:
|
||||
for path in sorted(owned, key=lambda p:p.name, reverse=True)[keep:]:
|
||||
path.unlink()
|
||||
|
||||
@staticmethod
|
||||
def month_before(now):
|
||||
year, month = (now.year, now.month-1) if now.month > 1 else (now.year-1, 12)
|
||||
return now.replace(year=year, month=month, day=min(now.day, calendar.monthrange(year, month)[1]))
|
||||
|
||||
def prune_cloud(self, instance, newest, result, now=None):
|
||||
now = now or dt.datetime.now(dt.timezone(dt.timedelta(hours=8)))
|
||||
cutoff = self.month_before(now)
|
||||
result.update(cloudRetentionMonths=1, cloudDeleted=0, cloudCutoff=cutoff.isoformat())
|
||||
root = '/apps/'+self.a.BAIDU.public_config()['appFolder']+'/数据库备份/'+instance
|
||||
pattern = re.compile('vision-'+re.escape(instance)+r'-(\d{8}T\d{12}Z)\.sqlite')
|
||||
# Collect all pages before deleting: changing a directory while paging can skip entries.
|
||||
for row in self.a.BAIDU.list_directory(root):
|
||||
path = row.get('path', '')
|
||||
if not path.startswith(root+'/'): continue
|
||||
name = path[len(root)+1:]; match = pattern.fullmatch(name)
|
||||
if not match: continue
|
||||
try: created = dt.datetime.strptime(match[1], '%Y%m%dT%H%M%S%fZ').replace(tzinfo=dt.timezone.utc)
|
||||
except ValueError: continue
|
||||
if created >= cutoff: continue
|
||||
candidates = self.a.BAIDU.list_directory(path) if row.get('isdir') == 1 else [row]
|
||||
for item in candidates:
|
||||
target = item.get('path', '')
|
||||
expected = path+'/'+name if row.get('isdir') == 1 else path
|
||||
if target != expected or target == newest or item.get('isdir') != 0: continue
|
||||
self.a.BAIDU.delete_backup_file(target)
|
||||
result['cloudDeleted'] += 1
|
||||
result['cloudCleanupOk'] = True
|
||||
|
||||
def run(self, reason='manual'):
|
||||
a = self.a
|
||||
result = dict(at=dt.datetime.now(dt.timezone.utc).isoformat(), reason=reason,
|
||||
@@ -155,9 +185,15 @@ class Backups:
|
||||
self.prune(network, prefix, config['keep'])
|
||||
if config['baidu']:
|
||||
self.stage = '正在上传百度网盘'
|
||||
remote = '/apps/'+a.BAIDU.public_config()['appFolder']+'/数据库备份/'+instance+'/'+name+'/'+name
|
||||
remote = '/apps/'+a.BAIDU.public_config()['appFolder']+'/数据库备份/'+instance+'/'+name
|
||||
uploaded = a.BAIDU.upload('shared', target, remote)
|
||||
self.stage = '正在校验百度网盘副本'
|
||||
a.BAIDU.verify_backup(remote, uploaded['fs_id'], result['bytes'], digest)
|
||||
result.update(baiduOk=True, baiduPath=remote, baiduFileId=str(uploaded['fs_id']))
|
||||
self.stage = '正在清理超过一个月的云端备份'
|
||||
try: self.prune_cloud(instance, remote, result)
|
||||
except Exception as error:
|
||||
result.update(cloudCleanupOk=False, cloudCleanupError=str(error)[:300] or '过期备份尚未清理')
|
||||
except Exception as error:
|
||||
result['error'] = str(error)[:300] or '备份未完成'
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user