diff --git a/.gitignore b/.gitignore index 505a3b1..a4dc8c1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ dist/ wheels/ *.egg-info +.ruff_cache/ # Virtual environments .venv diff --git a/main.py b/main.py index 1f1b1d6..26033cf 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,10 @@ +from sbackup import run + def main(): - print("Hello from sbackup!") + try: + run() + except KeyboardInterrupt: + print("\nExit.") if __name__ == "__main__": diff --git a/sbackup/__init__.py b/sbackup/__init__.py new file mode 100644 index 0000000..068160f --- /dev/null +++ b/sbackup/__init__.py @@ -0,0 +1,35 @@ +from .auto_save import add_folder, rm_folder, save_folder, all_folder +import sys + +VERSION = "0.0.1" +help_string = f""" +version: {VERSION} +为你的文件保驾护航. + +add 添加备份策略. +rm, remove 删除备份策略. +all 查看所有备份策略. +save 备份所有文件夹. +""" + +def run(): + command: str = sys.argv[1] + if command == "add": + add_folder(input("备份文件夹:"), input("目标文件夹:"), input("需要忽略的文件夹或文件(用" "隔开):")) + elif command == "rm" or command == "remove": + rm_folder(input("目标文件夹:")) + elif command == "all": + folders: dict[str, str] = all_folder() + if folders: + for key, value in folders.items(): + print(f"{key} to {value}.") + else: + print("没有配置任何备份策略.") + elif command == "save": + save_folder() + elif command == "version": + print(f"version: {VERSION}.") + elif command == "help": + print(help_string) + else: + print(f"没有名为 {command} 的命令.") \ No newline at end of file diff --git a/sbackup/auto_save.py b/sbackup/auto_save.py new file mode 100644 index 0000000..54b0ce0 --- /dev/null +++ b/sbackup/auto_save.py @@ -0,0 +1,53 @@ +import os +import json +from sbackup.pack import zip_folder + +data = {} + +def read_data(): + global data + if not os.path.exists("sbackup.json"): + with open("sbackup.json", "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + else: + with open("sbackup.json", "r", encoding="utf-8") as f: + data = json.load(f) + +def write_data(): + global data + with open("sbackup.json", "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + +def add_folder(folder_path: str, target_folder: str, skip_patterns: list[str] = [".git", "__pycache__"]): + read_data() + if not os.path.isdir(folder_path): + print(f"{folder_path} 不是有效的文件夹名或不存在.") + return + if not os.path.isdir(target_folder): + print(f"目标文件夹 {target_folder} 不是有效的文件夹或不存在.") + return + folder_path = os.path.abspath(folder_path) + if folder_path in data.keys(): + print(f"{folder_path} 已经添加过了,请勿重复添加.") + return + data[folder_path] = [os.stat(folder_path).st_mtime, os.path.abspath(target_folder), skip_patterns] + write_data() + +def rm_folder(folder_path: str): + read_data() + folder_path = os.path.abspath(folder_path) + if folder_path in data.keys(): + del data[folder_path] + write_data() + else: + print(f"未找到 {folder_path} 的备份策略.") + +def save_folder(): + read_data() + for key, value in data.items(): + if value[0] != os.stat(key).st_mtime: + zip_folder(key, value[1], value[2]) + +def all_folder() -> dict[str, str]: + read_data() + return {key: value[1] for key, value in data.items()} \ No newline at end of file diff --git a/sbackup/pack.py b/sbackup/pack.py new file mode 100644 index 0000000..4dbbba6 --- /dev/null +++ b/sbackup/pack.py @@ -0,0 +1,70 @@ +""" +@Time: 2025.12.20 +@Author: codeseed +""" + +import os +import zipfile +from fnmatch import fnmatch + + +def _should_ignore(name: str, patterns: list) -> bool: + """检查文件/目录名是否匹配任意忽略模式""" + for pattern in patterns: + if fnmatch(name, pattern): + return True + return False + + +def zip_folder( + folder_path: str, + zipfile_path: str = None, + skip_patterns: list[str] = [".git", "__pycache__"], +): + """ + 压缩文件夹为.zip文件 + + :param folder_path: 被压缩文件夹路径 + :type folder_path: str + :param zipfile_path: 目标.zip文件名,默认为被压缩文件夹名+.zip + :type zipfile_path: str + :param skip_patterns: 跳过压缩的文件或文件夹 + :type skip_patterns: list + """ + #print(folder_path) + #print(zipfile_path) + folder_path = os.path.abspath(folder_path) + if zipfile_path is None: + + zipfile_path = os.path.join( + os.path.dirname(folder_path), os.path.basename(folder_path) + ".zip" + ) + if not zipfile_path.lower().endswith(".zip"): + #print(os.path.splitext(zipfile_path)[0]) + if not os.path.splitext(zipfile_path)[0]: + zipfile_path = os.path.join( + os.path.dirname(folder_path), os.path.basename(folder_path) + ".zip" + ) + else: + zipfile_path = os.path.splitext(zipfile_path)[0] + ".zip" + if not os.path.isdir(folder_path): + #print(f"{folder_path} 不是一个有效的文件夹或不存在.") + return + #print(folder_path) + #print(zipfile_path) + try: + with zipfile.ZipFile(zipfile_path, "w", zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(folder_path): + dirs[:] = [d for d in dirs if not _should_ignore(d, skip_patterns)] + for file in files: + if _should_ignore(file, skip_patterns): + continue + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, os.path.dirname(folder_path)) + zipf.write(file_path, arcname) + except PermissionError: + print(f"权限不足:无法写入 '{zipfile_path}'") + except OSError as e: + print(f"系统错误:{e}") + except Exception as e: + print(f"未知错误:{e}") diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..41db365 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "sbackup" +version = "0.1.0" +source = { virtual = "." }