Release v0.1.29: media previews, clean composer, regional profiles and cloud retention

This commit is contained in:
Codex
2026-10-04 20:03:44 +08:00
parent 67709832c5
commit 693dd2a10a
17 changed files with 359 additions and 46 deletions
+37 -1
View File
@@ -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: