From 52b44367c03d18f9ef6f82a8cc7244ee6abbf59b Mon Sep 17 00:00:00 2001 From: matlag Date: Thu, 8 Aug 2024 08:09:16 -0400 Subject: [PATCH] Rewrite in class, cleaner --- README.md | 4 +- file_type.py | 23 +++++++++ link_fixer.py | 110 +++++++++++++++++++++++++++------------- swap_link.py | 47 ----------------- test/test_data.tar | Bin 0 -> 20480 bytes test/test_link_fixer.py | 79 ++++++++++++++++++++--------- 6 files changed, 156 insertions(+), 107 deletions(-) create mode 100644 file_type.py delete mode 100644 swap_link.py create mode 100644 test/test_data.tar diff --git a/README.md b/README.md index 80a439b..a3e61fa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # link-fixer -Fix broken symbolic links. Search for file of same name as original target in provided directory and link to that target if unique and if exists. \ No newline at end of file +Fix broken symbolic links. +Search for file of same name as original target in provided directory and link to that target if unique and if exists. +If pointed to a directory, link_fixer scans the directory for broken links. \ No newline at end of file diff --git a/file_type.py b/file_type.py new file mode 100644 index 0000000..866dc25 --- /dev/null +++ b/file_type.py @@ -0,0 +1,23 @@ +# file_type.py +# Identify type of file: file, directory, link, broken link + +from pathlib import Path + +class FileType(): + + @classmethod + def get_file_type(cls, filename): + # Return type of file ("file", "dir", "link", "broken-link", "other") + p = Path(filename) + if not p.exists(): + if p.is_symlink(): + return "broken-link" + else: + raise FileNotFound(errno.ENOENT, os.strerror(errno.ENOENT), filename) + if p.is_symlink(): + return "symlink" + if p.is_dir(): + return "directory" + elif p.is_file(): + return "file" + return "other" diff --git a/link_fixer.py b/link_fixer.py index 549b27e..ca4344a 100644 --- a/link_fixer.py +++ b/link_fixer.py @@ -1,47 +1,87 @@ import argparse import os +from os.path import relpath from pathlib import Path import sys +from file_type import FileType from search_file import search_file -from swap_link import swap_link -def link_fixer(ln_path, tgt_dir_path): - ln_is_dir = False +class Link(FileType): - link = Path(ln_path) - tgt_dir = Path(tgt_dir_path) + @classmethod + def fix(cls, ln_str, tgt_dir_str): - if link.is_dir(): - ln_is_dir = True - elif not link.is_symlink(): - if not link.exists(): - sys.exit("Link argument matches no file or directory") - else: - sys.exit("Link argument is not a symbolic link") - elif link.exists(): - sys.exit("Link is not broken!") + link = Path(ln_str) + tgt_dir = Path(tgt_dir_str) - if not tgt_dir.exists(): - sys.exit("Target directory not found.") - if not tgt_dir.is_dir(): - sys.exit("Pointed target is not a directory") + try: + to_be_fixed = cls.get_file_type(link) + except Exception as e: + #print(f"Error: could not access link {ln_str}.") + sys.exit(f"Error: could not access link {ln_str}.") - print("Starting link fixer") - if ln_is_dir: - print("Links dir: \t", link) - else: - print("Link: \t\t", link) - print("Targets dir: \t", tgt_dir) - if ln_is_dir: - sys.exit("But ln dir version not yet implemented. Sorry!") + if not tgt_dir.is_dir(): + sys.exit(f"Error: target dir {tgt_dir} does not seem to exist or be a directory. Abort.") - tgt = search_file(link.resolve().name, tgt_dir_path) - swap_link(ln_path, tgt) + match to_be_fixed: + case "file": + sys.exit(f"Error: link {ln_str} is not a link at all. Abort.") + case "symlink": + #print(f"Error: link {ln_str} is not broken. Abort.") + sys.exit(f"Error: link {ln_str} is not broken. Abort.") + case "broken-link": + try: + tgt = search_file(link.resolve().name, tgt_dir) + except Exception as e: + #print("No match for link reference filename in target directory.") + sys.exit("Error: no match for link target in {tgt_dir_str}") + cls._swap_link(link, tgt) + case "directory": + for root, dirs, files in os.walk(to_be_fixed): + for name in files: + if get_file_type(name) == "broken-link": + cls.link_fixer(name, tgt_dir_path) + + @classmethod + def _swap_link(cls, lnk, tgt): + # relnk lnk to tgt as symlink, relative path + # assumes type verifications already made! + #lnk: symbolic link to swap, Path + #tgt: target for new link, Path + + tpath = Path(tgt) + if tpath.is_symlink(): + if not tpath.exists(): + raise Exception("Target is also a broken link!") + elif tpath.readlink().resolve() == lnpath.resolve() : + raise Exception("Target is a link to link to be fixed...") + + tmp_suffix = 0 + tmp_prefix = 'temp' + tmp_name = tmp_prefix + str(tmp_suffix) + tmp_path = Path(lnk.parent / tmp_name) + while tmp_path.exists() or tmp_path.is_symlink(): + tmp_suffix += 1 + tmp_name = tmp_prefix + str(tmp_suffix) + tmp_path = Path(lnk.parent / tmp_name) + + sym_path = relpath(tgt, lnk.resolve())[3:] + + try: + tmp_path.symlink_to(sym_path) + except Exception as e: + print(f"Attempted to create tmp link: {tmp_name}") + #raise Exception("Failed to create new symlink. Check permissions!") + + try: + lnk.unlink() + except Exception: + tmp_path.unlink() + #raise Exception("Failed to remove broken link. Check permissions!") + + tmp_path.rename(lnk) -def fix_link(lnk, tgt_dir_path): - return True - if __name__ == "__main__": @@ -50,13 +90,13 @@ if __name__ == "__main__": Search for file with same name as link in target dir. Replace link to point to found file if any. ''' - ) parser.add_argument("link", type=str, help="Broken link, or directory with broken links") parser.add_argument("tgt_path", type=str, help="Directory in which to find target(s)") args = parser.parse_args() - link = Path(args.link) - tgt_dir = Path(args.tgt_path) + link = args.link + tgt_dir = args.tgt_path + + Link.fix(link, tgt_dir) - link_fixer(link, tgt_dir) diff --git a/swap_link.py b/swap_link.py deleted file mode 100644 index f2afc1b..0000000 --- a/swap_link.py +++ /dev/null @@ -1,47 +0,0 @@ -# swap_link.py -# Replace a link file with another one pointing to a new target -# - -from pathlib import Path -from os.path import relpath # won't be necessary with Python 3.12 thanks to walk_up arg in relative_to() - -def swap_link(lnk, tgt): - #lnk: symbolic link to swap, str - #tgt: target for new link, str - lnpath = Path(lnk) - if not lnpath.is_symlink(): - raise Exception("First argument is not a symbolic link") - elif lnpath.exists(): - raise Exception("Link is not broken") - - tpath = Path(tgt) - if tpath.is_symlink(): - if not tpath.exists(): - raise Exception("Target is also a broken link!") - elif tpath.readlink().resolve() == lnpath.resolve() : - raise Exception("Target is a link to link to be fixed...") - - tmp_suffix = 0 - tmp_prefix = 'temp' - tmp_name = tmp_prefix + str(tmp_suffix) - tmp_path = Path(lnpath.parent / tmp_name) - while tmp_path.exists() or tmp_path.is_symlink(): - tmp_suffix += 1 - tmp_name = tmp_prefix + str(tmp_suffix) - tmp_path = Path(lnk.parent / tmp_name) - - sym_path = relpath(tgt, lnk)[3:] - - try: - tmp_path.symlink_to(sym_path) - except Exception: - print(f"Attempted to create tmp link: {tmp_name}") - raise Exception("Failed to create new symlink. Check permissions!") - - try: - lnpath.unlink() - except Exception: - tmp_path.unlink() - raise Exception("Failed to remove broken link. Check permissions!") - - tmp_path.rename(lnk) \ No newline at end of file diff --git a/test/test_data.tar b/test/test_data.tar new file mode 100644 index 0000000000000000000000000000000000000000..adbcb0c3386918e439ea050df8e269accc213c6a GIT binary patch literal 20480 zcmeI2VRG6q5QTjdp8%wlWF3cb4FM(wI@lyVes}FSpdkiRq_i`A`lA?uP` z+j5?zZHk-sF5^ynr!IRLl9p*(n8GV%tj?6y+iKG;QweM-WSP{jYnd-(r`Y|)0s^^fa+ z&w}zcFy#HAm#snCkpFxANn5@K%@8S?*E`9Eg*7u_f4e=$D$Upx4JBw*vd zeC7W>|Ld5>6aA;=|6YHu3Y`Dr3OzF0i2f8Y{XgV?=S}Z_KL4TqGa_2Vmq+!Fn0O!Z zzuf=HlH&beUo9@;q6UV@|6k>Qn!j(`P2T)0tJTxBphxt##C0O=J+c0%{|rHzuJ0_? zuG`30zF@(k|AhLF^#vDxmDK;q8-@KpfSBkZ&|l)9e4NY4N8 zf5T9&z2p-tb)x^BafU1x{>1-;`ftE6)kB~^-~CdA!Xx^dqVvDvV<67|sYJh+ zcTE50uv;yp7JTvEKmD`U|8D)S^M9v5X|4lu{V!2I3jPnA|I7Jc`g*^3E9>nt0QCIB zME%d_KjQMwNBws=nkEYc=zrEf5#)*bpYuPF(&B%v|Gys(`*n9YNwhfVf7YM