import argparse from pathlib import Path import os SAFETY = 'jellyfin' def get_args(): parser = argparse.ArgumentParser() parser.add_argument('source', type=Path) parser.add_argument('dest', type=Path) parser.add_argument('--dry', help='Only output changes', action="store_true") return parser.parse_args() def main(source: Path, dest: Path, dry: bool): remove_deleted_files(source, dest, dry) create_new_links(source, dest, dry) def remove_deleted_files(source: Path, dest: Path, dry: bool): for file in dest.iterdir(): if file.is_dir(): original_dir = source / file.name if original_dir.is_dir(): remove_deleted_files(original_dir, file, dry) else: remove_deleted_files(original_dir, file, dry) if dry: print(f"Remove directory: {file}") else: file.rmdir() # will fail if files have since been created elif file.is_file(): original_file = source / file.name if not original_file.exists() or original_file.is_dir(): if SAFETY not in str(file): raise Exception("Deletion path does not include jellyfin") if dry: print(f"Remove file: {file}") else: file.unlink() def create_new_links(source: Path, dest: Path, dry: bool): for file in source.iterdir(): if file.is_dir(): new_dest = dest / file.name if not new_dest.exists(): if dry: print(f"Create directory: {new_dest}") else: new_dest.mkdir() elif new_dest.exists() and not new_dest.is_dir(): raise Exception("Directory expected") create_new_links(file, new_dest, dry) elif file.is_file(): new_file = dest / file.name if not new_file.exists(): if dry: print(f"Create file: {new_file}") else: try: file.copy(new_file) except AttributeError: os.system(f'cp --reflink "{file}" "{new_file}"') if __name__ == '__main__': args = get_args() source_directory = args.source destination_directory = args.dest dryrun = args.dry if len(source_directory.parts) < 3 or len(destination_directory.parts) < 3: raise Exception("Paths too short") if dryrun: print("Dry run - no changes will be made") main(source_directory, destination_directory, dryrun) if dryrun: print("Dry run - no changes have been made")