From 96b6bd1af505e7c334dea06708b79fabb6210705 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 10 Aug 2026 18:00:00 +0800 Subject: [PATCH] =?UTF-8?q?sz-backup:=20tar=20=E5=90=8E=207z=20=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9=20(=E4=B8=8E=E4=B8=8A=E4=BC=A0=E4=B8=80=E8=87=B4,=20?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E7=B1=BB=20mx=3D9=20=E7=85=A7=E7=89=87=20mx?= =?UTF-8?q?=3D1);=20=E6=81=A2=E5=A4=8D=E7=AB=AF=207z=20x=20=E8=A7=A3?= =?UTF-8?q?=E5=8E=8B;=20=E4=BF=AE=20manifest=20=E4=BF=9D=E5=AD=98=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backup.py | 30 +++++++++++++++---- debian/7z-encrypt-client-bin/DEBIAN/control | 2 +- debian/7z-encrypt-client-bin/DEBIAN/md5sums | 2 +- debian/7z-encrypt-client/DEBIAN/control | 2 +- debian/7z-encrypt-client/DEBIAN/md5sums | 2 +- .../usr/share/7z-encrypt/backup.py | 30 +++++++++++++++---- 6 files changed, 54 insertions(+), 14 deletions(-) diff --git a/backup.py b/backup.py index b0db614..22d06c5 100644 --- a/backup.py +++ b/backup.py @@ -28,10 +28,17 @@ from tqdm import tqdm STATE_DIR = Path.home() / ".local/share/7z-encrypt" / "backup" BATCH_MB = 500 # 每批 tar 上限 BATCH_FILES = 500 # 每批文件数上限 -# 高压缩率类型 (聊天记录/文档) 用 gzip, 照片/视频直接存 +# 高压缩率类型 (聊天记录/文档) 用 -mx=9, 照片/视频批次 -mx=1 快速 COMPRESS_SUFFIX = {".db", ".sqlite", ".sqlite3", ".sql", ".txt", ".json", ".xml", ".csv", ".log", ".md", ".html"} +def _7z(args: list[str]) -> None: + """调系统 7z (p7zip-full), 失败抛异常""" + r = subprocess.run(["7z"] + args, capture_output=True, text=True, timeout=7200) + if r.returncode != 0: + raise RuntimeError(f"7z 失败: {r.stderr.strip()[-200:] or r.stdout.strip()[-200:]}") + + def _cfg() -> dict[str, Any]: """读配置文件 backup 段 (XDG, 同客户端)""" env = os.environ.get("SZ_CONFIG") @@ -201,13 +208,19 @@ def backup(name: str, chunk_mb: int = 10) -> int: for i, batch in enumerate(batches, 1): tar_path = STATE_DIR / f"{name}_{ts}_{i:02d}.tar" tar_path.parent.mkdir(parents=True, exist_ok=True) - mode = "w:gz" if any(_compress(f) for f in batch) else "w" - with tarfile.open(tar_path, mode) as tf: + with tarfile.open(tar_path, "w") as tf: for f in batch: try: tf.add(f, arcname=str(f)) except (OSError, tarfile.TarError) as e: print(f" [跳过] {f}: {e}") + # 7z 压缩 (与上传文件一致): 含文本类 -mx=9, 照片视频 -mx=1 + mx = 9 if any(_compress(f) for f in batch) else 1 + seven = tar_path.with_suffix(".tar.7z") + print(f"[备份] 7z 压缩 (mx={mx}) ...") + _7z(["a", "-y", "-bd", f"-mx={mx}", str(seven), str(tar_path)]) + tar_path.unlink(missing_ok=True) + tar_path = seven size_mb = tar_path.stat().st_size / 1048576 print(f"[备份] 批次 {i}/{len(batches)}: {tar_path.name} ({size_mb:.1f} MB), 上传中 ...") try: @@ -215,7 +228,7 @@ def backup(name: str, chunk_mb: int = 10) -> int: except RuntimeError as e: print(f"[错误] {e}") return 1 - files_in_batch = [str(f) for f in batch if tarfile.open(tar_path).getnames()] + files_in_batch = [str(f) for f in batch] for f in batch: try: st = f.stat() @@ -267,7 +280,14 @@ def restore(name: str, out_root: Path | None = None) -> int: except Exception as e: print(f"[错误] 下载 {tar_name} 失败: {e}") return 1 - # 解包 (路径校验: 只解回 manifest 记录的文件) + # 7z 解压 -> tar -> 解包 (路径校验: 只解回 manifest 记录的文件) + if tar_src.suffix == ".7z": + _7z(["x", "-y", "-bd", f"-o{dl}", str(tar_src)]) + tar_src.unlink(missing_ok=True) + tar_src = dl / tar_name + if not tar_src.exists(): + print(f"[错误] 未找到 tar: {dl}") + return 1 with tarfile.open(tar_src) as tf: for member in tf.getmembers(): if member.name not in info.get("files", []): diff --git a/debian/7z-encrypt-client-bin/DEBIAN/control b/debian/7z-encrypt-client-bin/DEBIAN/control index 14ab16b..8079282 100644 --- a/debian/7z-encrypt-client-bin/DEBIAN/control +++ b/debian/7z-encrypt-client-bin/DEBIAN/control @@ -3,7 +3,7 @@ Source: 7z-encrypt-client Version: 1.0.0 Architecture: amd64 Maintainer: edgevoid -Installed-Size: 51850 +Installed-Size: 51858 Depends: p7zip-full Conflicts: 7z-encrypt-client Replaces: 7z-encrypt-client diff --git a/debian/7z-encrypt-client-bin/DEBIAN/md5sums b/debian/7z-encrypt-client-bin/DEBIAN/md5sums index e668ad1..4e54375 100644 --- a/debian/7z-encrypt-client-bin/DEBIAN/md5sums +++ b/debian/7z-encrypt-client-bin/DEBIAN/md5sums @@ -1,4 +1,4 @@ -094c9453f9346a962da07a6e123cc5fc usr/bin/sz-backup +b7ec4ffff510e79070edf4685f0ba9a1 usr/bin/sz-backup 61901c8e9af062eef5c386c715f5f3bb usr/bin/sz-config 02b57eea9c5ca54c63fd82da1d6d960b usr/bin/sz-transfer 8f9902ebfd228e08f7691abe6d471d86 usr/bin/sz-tui diff --git a/debian/7z-encrypt-client/DEBIAN/control b/debian/7z-encrypt-client/DEBIAN/control index 179c432..5df66e1 100644 --- a/debian/7z-encrypt-client/DEBIAN/control +++ b/debian/7z-encrypt-client/DEBIAN/control @@ -2,7 +2,7 @@ Package: 7z-encrypt-client Version: 1.0.0 Architecture: all Maintainer: edgevoid -Installed-Size: 116 +Installed-Size: 117 Depends: python3 (>= 3.10), python3-venv, p7zip-full Section: utils Priority: optional diff --git a/debian/7z-encrypt-client/DEBIAN/md5sums b/debian/7z-encrypt-client/DEBIAN/md5sums index 1c91033..f0bcfd1 100644 --- a/debian/7z-encrypt-client/DEBIAN/md5sums +++ b/debian/7z-encrypt-client/DEBIAN/md5sums @@ -2,7 +2,7 @@ 440c53f84f5722b5146959c8fc6d7a23 usr/bin/sz-config ec8cf0df841e479689ccb681f6168ccc usr/bin/sz-transfer 0ba5b8147990daa7fd07344952c42847 usr/bin/sz-tui -3ed341805839f8dcee1fc8f719c5cc2f usr/share/7z-encrypt/backup.py +78aed672b874d47ecb3e9dc12b6951a8 usr/share/7z-encrypt/backup.py dcb31e74e0a6ee8073337fc4fb2acf99 usr/share/7z-encrypt/config.py 8a064c0b3c21d50812a3fd69a320d862 usr/share/7z-encrypt/crypto.py 21062e04f8c7f4471881a39c489dfa73 usr/share/7z-encrypt/metadata.py diff --git a/debian/7z-encrypt-client/usr/share/7z-encrypt/backup.py b/debian/7z-encrypt-client/usr/share/7z-encrypt/backup.py index b0db614..22d06c5 100644 --- a/debian/7z-encrypt-client/usr/share/7z-encrypt/backup.py +++ b/debian/7z-encrypt-client/usr/share/7z-encrypt/backup.py @@ -28,10 +28,17 @@ from tqdm import tqdm STATE_DIR = Path.home() / ".local/share/7z-encrypt" / "backup" BATCH_MB = 500 # 每批 tar 上限 BATCH_FILES = 500 # 每批文件数上限 -# 高压缩率类型 (聊天记录/文档) 用 gzip, 照片/视频直接存 +# 高压缩率类型 (聊天记录/文档) 用 -mx=9, 照片/视频批次 -mx=1 快速 COMPRESS_SUFFIX = {".db", ".sqlite", ".sqlite3", ".sql", ".txt", ".json", ".xml", ".csv", ".log", ".md", ".html"} +def _7z(args: list[str]) -> None: + """调系统 7z (p7zip-full), 失败抛异常""" + r = subprocess.run(["7z"] + args, capture_output=True, text=True, timeout=7200) + if r.returncode != 0: + raise RuntimeError(f"7z 失败: {r.stderr.strip()[-200:] or r.stdout.strip()[-200:]}") + + def _cfg() -> dict[str, Any]: """读配置文件 backup 段 (XDG, 同客户端)""" env = os.environ.get("SZ_CONFIG") @@ -201,13 +208,19 @@ def backup(name: str, chunk_mb: int = 10) -> int: for i, batch in enumerate(batches, 1): tar_path = STATE_DIR / f"{name}_{ts}_{i:02d}.tar" tar_path.parent.mkdir(parents=True, exist_ok=True) - mode = "w:gz" if any(_compress(f) for f in batch) else "w" - with tarfile.open(tar_path, mode) as tf: + with tarfile.open(tar_path, "w") as tf: for f in batch: try: tf.add(f, arcname=str(f)) except (OSError, tarfile.TarError) as e: print(f" [跳过] {f}: {e}") + # 7z 压缩 (与上传文件一致): 含文本类 -mx=9, 照片视频 -mx=1 + mx = 9 if any(_compress(f) for f in batch) else 1 + seven = tar_path.with_suffix(".tar.7z") + print(f"[备份] 7z 压缩 (mx={mx}) ...") + _7z(["a", "-y", "-bd", f"-mx={mx}", str(seven), str(tar_path)]) + tar_path.unlink(missing_ok=True) + tar_path = seven size_mb = tar_path.stat().st_size / 1048576 print(f"[备份] 批次 {i}/{len(batches)}: {tar_path.name} ({size_mb:.1f} MB), 上传中 ...") try: @@ -215,7 +228,7 @@ def backup(name: str, chunk_mb: int = 10) -> int: except RuntimeError as e: print(f"[错误] {e}") return 1 - files_in_batch = [str(f) for f in batch if tarfile.open(tar_path).getnames()] + files_in_batch = [str(f) for f in batch] for f in batch: try: st = f.stat() @@ -267,7 +280,14 @@ def restore(name: str, out_root: Path | None = None) -> int: except Exception as e: print(f"[错误] 下载 {tar_name} 失败: {e}") return 1 - # 解包 (路径校验: 只解回 manifest 记录的文件) + # 7z 解压 -> tar -> 解包 (路径校验: 只解回 manifest 记录的文件) + if tar_src.suffix == ".7z": + _7z(["x", "-y", "-bd", f"-o{dl}", str(tar_src)]) + tar_src.unlink(missing_ok=True) + tar_src = dl / tar_name + if not tar_src.exists(): + print(f"[错误] 未找到 tar: {dl}") + return 1 with tarfile.open(tar_src) as tf: for member in tf.getmembers(): if member.name not in info.get("files", []):