from genericpath import exists from typing import Iterable from enum import Enum from math import nan import os from os import path from pathlib import Path #=== Init ===# backup_root: str = path.dirname(__file__) user_home: str = path.expanduser("~") if user_home == "~": print("WARN: Cannot read the user home dir, do you run it in the correct script?") exit() else: print("dot-config: current user home: " + user_home) class SysType (Enum): LINUX = "linux" TERMUX = "termux" WINDOWS = "windows" if ("termux" in backup_root): sys_type: SysType = SysType.TERMUX elif (backup_root[0] == "/"): sys_type: SysType = SysType.LINUX else: sys_type: SysType = SysType.WINDOWS print(f"dot config: your dot-config path is {backup_root}") print(f"dot config: your system type is {sys_type}") print(f"Is all the informations correct? [y/n] ", end="") while True: _in = input() match _in: case "y": print("continuing...") break; case "n": print("Exiting") exit() case _: print("please confirm with [y/n] ", end="") #=== Utils ===# def sorted_paths (paths: Iterable[str]) -> list[str]: fin = sorted(paths) return fin def de_abs_path (path: str) -> str: return path.strip('/').strip('\\') #=== Backup Item ===# class BackupItem: def __init__ (self, backup_dir: str, origin_dir: str) -> None: self.name: str = backup_dir self.backup_dir: str = path.join(backup_root, backup_dir) self.origin_dir: str = path.abspath(path.expanduser(origin_dir)) table: list[BackupItem] = [ BackupItem("PowerShell", "~/Documents/PowerShell"), BackupItem("lsd", "~/.config/lsd") ] def execute_sync (backupItem: BackupItem) -> None: print(f">>> executing backup for {backupItem.name}") all_files_tmp: list[str] = [] def walk_dir (walking_dir: str): for root, dirs, files in os.walk(walking_dir): common_root: str = path.commonpath([root, walking_dir]) if common_root == walking_dir: relative_root: str = root[len(walking_dir):] else: print(f"WARN: cannot find common root for {root} and {walking_dir}, will break this dir.") continue for file in files: relative_file_path = de_abs_path(path.join(relative_root, file)) # print(f"find file in source: {`relative_file_path`}") all_files_tmp.append(relative_file_path) walk_dir(backupItem.origin_dir) walk_dir(backupItem.backup_dir) all_files: list[str] = sorted_paths(set(all_files_tmp)) for file in all_files: compare_file(backupItem, file) # print("\n".join(all_files)) def compare_file (rootBackItem: BackupItem, relative_file_path: str) -> None: class IsNewerStatus (Enum): OLDER = -1 NEWER = 1 DIFFERENT = nan SAME = 0 class FileStatus: def __init__(self, realpath: str) -> None: self.path = realpath self.exists = path.exists(realpath) if self.exists: self.size = path.getsize(realpath) self.edited_time = path.getmtime(realpath) def isNewerThan (self, other): # type: (FileStatus) -> IsNewerStatus if not self.exists: return IsNewerStatus.OLDER if not other.exists: return IsNewerStatus.NEWER if self.edited_time > other.edited_time: return IsNewerStatus.NEWER elif self.edited_time < other.edited_time: return IsNewerStatus.OLDER if self.size != other.size: return IsNewerStatus.DIFFERENT return IsNewerStatus.SAME backup_item: FileStatus = FileStatus(path.join(rootBackItem.backup_dir, relative_file_path)) origin_item: FileStatus = FileStatus(path.join(rootBackItem.origin_dir, relative_file_path)) match origin_item.isNewerThan(backup_item): case IsNewerStatus.SAME: print(f"{relative_file_path} : is same") case IsNewerStatus.OLDER: print(f"{relative_file_path} : backup file is newer") case IsNewerStatus.NEWER: print(f"{relative_file_path} : original file is newer") case IsNewerStatus.DIFFERENT: print(f"{relative_file_path} : WARN : backup is different but cannot determine which is newer") #=== main ===# for i in table: execute_sync(i)