From 8b4bf8d51f1486bd0663229d61842b210b7aed13 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Sat, 27 Apr 2024 19:25:33 -0700 Subject: [PATCH] Allow preserving the original filename when moving --- comictaggerlib/cli.py | 8 +-- comictaggerlib/ctsettings/file.py | 7 ++- .../ctsettings/settngs_namespace.py | 6 ++- comictaggerlib/filerenamer.py | 19 ++++--- comictaggerlib/renamewindow.py | 7 +-- comictaggerlib/settingswindow.py | 25 +++++++-- comictaggerlib/ui/settingswindow.ui | 15 ++++-- testing/filenames.py | 54 ++++++++++++++++--- tests/rename_test.py | 14 ++--- 9 files changed, 118 insertions(+), 37 deletions(-) diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py index 72d7c7d..fe385a6 100644 --- a/comictaggerlib/cli.py +++ b/comictaggerlib/cli.py @@ -573,14 +573,16 @@ class CLI: new_ext = ca.extension() renamer = FileRenamer( - md, + None, platform="universal" if self.config.File_Rename__strict else "auto", replacements=self.config.File_Rename__replacements, ) + renamer.set_metadata(md, ca.path.name) renamer.set_template(self.config.File_Rename__template) renamer.set_issue_zero_padding(self.config.File_Rename__issue_number_padding) renamer.set_smart_cleanup(self.config.File_Rename__use_smart_string_cleanup) - renamer.move = self.config.File_Rename__move_to_dir + renamer.move = self.config.File_Rename__move + renamer.move_only = self.config.File_Rename__only_move try: new_name = renamer.determine_name(ext=new_ext) @@ -600,7 +602,7 @@ class CLI: logger.exception("Formatter failure: %s metadata: %s", self.config.File_Rename__template, renamer.metadata) return Result(Action.rename, Status.rename_failure, original_path, md=md) - folder = get_rename_dir(ca, self.config.File_Rename__dir if self.config.File_Rename__move_to_dir else None) + folder = get_rename_dir(ca, self.config.File_Rename__dir if self.config.File_Rename__move else None) full_path = folder / new_name diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py index 8dbadab..d1bb276 100644 --- a/comictaggerlib/ctsettings/file.py +++ b/comictaggerlib/ctsettings/file.py @@ -201,11 +201,16 @@ def rename(parser: settngs.Manager) -> None: parser.add_setting("--dir", default="", help="The directory to move renamed files to") parser.add_setting( "--move", - dest="move_to_dir", default=False, action=argparse.BooleanOptionalAction, help="Enables moving renamed files to a separate directory", ) + parser.add_setting( + "--only-move", + default=False, + action=argparse.BooleanOptionalAction, + help="Ignores the filename when moving renamed files to a separate directory", + ) parser.add_setting( "--strict", default=False, diff --git a/comictaggerlib/ctsettings/settngs_namespace.py b/comictaggerlib/ctsettings/settngs_namespace.py index c0b3a8c..5f1aaf8 100644 --- a/comictaggerlib/ctsettings/settngs_namespace.py +++ b/comictaggerlib/ctsettings/settngs_namespace.py @@ -88,7 +88,8 @@ class SettngsNS(settngs.TypedNS): File_Rename__use_smart_string_cleanup: bool File_Rename__auto_extension: bool File_Rename__dir: str - File_Rename__move_to_dir: bool + File_Rename__move: bool + File_Rename__only_move: bool File_Rename__strict: bool File_Rename__replacements: comictaggerlib.defaults.Replacements @@ -205,7 +206,8 @@ class File_Rename(typing.TypedDict): use_smart_string_cleanup: bool auto_extension: bool dir: str - move_to_dir: bool + move: bool + only_move: bool strict: bool replacements: comictaggerlib.defaults.Replacements diff --git a/comictaggerlib/filerenamer.py b/comictaggerlib/filerenamer.py index 1760e19..af92255 100644 --- a/comictaggerlib/filerenamer.py +++ b/comictaggerlib/filerenamer.py @@ -38,8 +38,8 @@ def get_rename_dir(ca: ComicArchive, rename_dir: str | pathlib.Path | None) -> p folder = ca.path.parent.absolute() if rename_dir is not None: if isinstance(rename_dir, str): - rename_dir = rename_dir.strip() - folder = pathlib.Path(rename_dir).absolute() + rename_dir = pathlib.Path(rename_dir.strip()) + folder = rename_dir.absolute() return folder @@ -192,9 +192,12 @@ class FileRenamer: self.move = False self.platform = platform self.replacements = replacements + self.original_name = "" + self.move_only = False - def set_metadata(self, metadata: GenericMetadata) -> None: + def set_metadata(self, metadata: GenericMetadata, original_name: str) -> None: self.metadata = metadata + self.original_name = original_name def set_issue_zero_padding(self, count: int) -> None: self.issue_zero_padding = count @@ -240,9 +243,9 @@ class FileRenamer: ).strip() new_name = os.path.join(new_name, new_basename) - new_name += ext - new_basename += ext - + if self.move_only: + new_folder = os.path.join(new_name, os.path.splitext(self.original_name)[0]) + return new_folder + ext if self.move: - return new_name.strip() - return new_basename.strip() + return new_name.strip() + ext + return new_basename.strip() + ext diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py index 35fc734..ae7efdf 100644 --- a/comictaggerlib/renamewindow.py +++ b/comictaggerlib/renamewindow.py @@ -75,6 +75,7 @@ class RenameWindow(QtWidgets.QDialog): self.renamer.set_issue_zero_padding(self.config[0].File_Rename__issue_number_padding) self.renamer.set_smart_cleanup(self.config[0].File_Rename__use_smart_string_cleanup) self.renamer.replacements = self.config[0].File_Rename__replacements + self.renamer.move_only = self.config[0].File_Rename__only_move new_ext = ca.path.suffix # default if self.config[0].File_Rename__auto_extension: @@ -89,8 +90,8 @@ class RenameWindow(QtWidgets.QDialog): self.config[0].Filename_Parsing__remove_fcbd, self.config[0].Filename_Parsing__remove_publisher, ) - self.renamer.set_metadata(md) - self.renamer.move = self.config[0].File_Rename__move_to_dir + self.renamer.set_metadata(md, ca.path.name) + self.renamer.move = self.config[0].File_Rename__move return new_ext def do_preview(self) -> None: @@ -192,7 +193,7 @@ class RenameWindow(QtWidgets.QDialog): folder = get_rename_dir( comic[0], - self.config[0].File_Rename__dir if self.config[0].File_Rename__move_to_dir else None, + self.config[0].File_Rename__dir if self.config[0].File_Rename__move else None, ) full_path = folder / comic[1] diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 1f2cb08..dc2d2bc 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -210,6 +210,7 @@ class SettingsWindow(QtWidgets.QDialog): self.btnResetSettings.clicked.connect(self.reset_settings) self.btnTemplateHelp.clicked.connect(self.show_template_help) self.cbxMoveFiles.clicked.connect(self.dir_test) + self.cbxMoveOnly.clicked.connect(self.move_only_clicked) self.leDirectory.textEdited.connect(self.dir_test) self.cbFilenameParser.currentIndexChanged.connect(self.switch_parser) @@ -220,6 +221,7 @@ class SettingsWindow(QtWidgets.QDialog): self.leRenameTemplate.textEdited.connect(self.rename_test) self.cbxMoveFiles.clicked.connect(self.rename_test) + self.cbxMoveOnly.clicked.connect(self.rename_test) self.cbxRenameStrict.clicked.connect(self.rename_test) self.cbxSmartCleanup.clicked.connect(self.rename_test) self.cbxChangeExtension.clicked.connect(self.rename_test) @@ -248,6 +250,7 @@ class SettingsWindow(QtWidgets.QDialog): self.cbxChangeExtension.clicked.disconnect() self.cbFilenameParser.currentIndexChanged.disconnect() self.cbxMoveFiles.clicked.disconnect() + self.cbxMoveOnly.clicked.disconnect() self.cbxRenameStrict.clicked.disconnect() self.cbxSmartCleanup.clicked.disconnect() self.leDirectory.textEdited.disconnect() @@ -336,19 +339,31 @@ class SettingsWindow(QtWidgets.QDialog): def rename_test(self, *args: Any, **kwargs: Any) -> None: self._rename_test(self.leRenameTemplate.text()) + def move_only_clicked(self, *args: Any, **kwargs: Any) -> None: + if self.cbxMoveOnly.isChecked(): + self.cbxMoveFiles.setEnabled(False) + self.cbxMoveFiles.setChecked(True) + else: + self.cbxMoveFiles.setEnabled(True) + self.dir_test() + def dir_test(self) -> None: self.lblDir.setText( - str(pathlib.Path(self.leDirectory.text().strip()).resolve()) if self.cbxMoveFiles.isChecked() else "" + str(pathlib.Path(self.leDirectory.text().strip()).resolve()) + if self.cbxMoveFiles.isChecked() or self.cbxMoveOnly.isChecked() + else "" ) def _rename_test(self, template: str) -> None: if not str(self.leIssueNumPadding.text()).isdigit(): self.leIssueNumPadding.setText("0") fr = FileRenamer( - md_test, + None, platform="universal" if self.cbxRenameStrict.isChecked() else "auto", replacements=self.get_replacements(), ) + fr.set_metadata(md_test, "cory doctorow #1.cbz") + fr.move_only = self.cbxMoveOnly.isChecked() fr.move = self.cbxMoveFiles.isChecked() fr.set_template(template) fr.set_issue_zero_padding(int(self.leIssueNumPadding.text())) @@ -418,7 +433,8 @@ class SettingsWindow(QtWidgets.QDialog): self.leIssueNumPadding.setText(str(self.config[0].File_Rename__issue_number_padding)) self.cbxSmartCleanup.setChecked(self.config[0].File_Rename__use_smart_string_cleanup) self.cbxChangeExtension.setChecked(self.config[0].File_Rename__auto_extension) - self.cbxMoveFiles.setChecked(self.config[0].File_Rename__move_to_dir) + self.cbxMoveFiles.setChecked(self.config[0].File_Rename__move) + self.cbxMoveOnly.setChecked(self.config[0].File_Rename__only_move) self.leDirectory.setText(self.config[0].File_Rename__dir) self.cbxRenameStrict.setChecked(self.config[0].File_Rename__strict) @@ -543,7 +559,8 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].File_Rename__issue_number_padding = int(self.leIssueNumPadding.text()) self.config[0].File_Rename__use_smart_string_cleanup = self.cbxSmartCleanup.isChecked() self.config[0].File_Rename__auto_extension = self.cbxChangeExtension.isChecked() - self.config[0].File_Rename__move_to_dir = self.cbxMoveFiles.isChecked() + self.config[0].File_Rename__move = self.cbxMoveFiles.isChecked() + self.config[0].File_Rename__only_move = self.cbxMoveOnly.isChecked() self.config[0].File_Rename__dir = self.leDirectory.text() self.config[0].File_Rename__strict = self.cbxRenameStrict.isChecked() diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui index 4a2d206..d5a4af6 100644 --- a/comictaggerlib/ui/settingswindow.ui +++ b/comictaggerlib/ui/settingswindow.ui @@ -658,7 +658,7 @@ - + Destination Directory: @@ -668,10 +668,10 @@ - + - + If checked will ensure reserved characters and filenames are removed for all Operating Systems.<br/>By default only removes restricted characters and filenames for the current Operating System. @@ -681,9 +681,16 @@ - + + + + + Only Move files when renaming + + + diff --git a/testing/filenames.py b/testing/filenames.py index 399f2a8..6c858fe 100644 --- a/testing/filenames.py +++ b/testing/filenames.py @@ -1032,10 +1032,11 @@ for p in names: ) ) -rnames = [ +file_renames = [ ( "{series!c} {price} {year}", # Capitalize False, + False, "universal", "Cory doctorow's futuristic tales of the here and now 2007.cbz", does_not_raise(), @@ -1043,6 +1044,7 @@ rnames = [ ( "{series!t} {price} {year}", # Title Case False, + False, "universal", "Cory Doctorow'S Futuristic Tales Of The Here And Now 2007.cbz", does_not_raise(), @@ -1050,6 +1052,7 @@ rnames = [ ( "{series!S} {price} {year}", # Swap Case False, + False, "universal", "cORY dOCTOROW'S fUTURISTIC tALES OF THE hERE AND nOW 2007.cbz", does_not_raise(), @@ -1057,6 +1060,7 @@ rnames = [ ( "{title!l} {price} {year}", # Lowercase False, + False, "universal", "anda's game 2007.cbz", does_not_raise(), @@ -1064,6 +1068,7 @@ rnames = [ ( "{title!u} {price} {year}", # Upper Case False, + False, "universal", "ANDA'S GAME 2007.cbz", does_not_raise(), @@ -1071,6 +1076,7 @@ rnames = [ ( "{title} {price} {year+}", # Empty alternate value False, + False, "universal", "Anda's Game.cbz", does_not_raise(), @@ -1078,6 +1084,7 @@ rnames = [ ( "{title} {price} {year+year!u}", # Alternate value Upper Case False, + False, "universal", "Anda's Game YEAR.cbz", does_not_raise(), @@ -1085,6 +1092,7 @@ rnames = [ ( "{title} {price} {year+year}", # Alternate Value False, + False, "universal", "Anda's Game year.cbz", does_not_raise(), @@ -1092,6 +1100,7 @@ rnames = [ ( "{title} {price-0} {year}", # Default value False, + False, "universal", "Anda's Game 0 2007.cbz", does_not_raise(), @@ -1099,6 +1108,7 @@ rnames = [ ( "{title} {price+0} {year}", # Alternate Value False, + False, "universal", "Anda's Game 2007.cbz", does_not_raise(), @@ -1106,6 +1116,7 @@ rnames = [ ( "{series} #{issue} - {title} ({year}) ({price})", # price should be none False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1113,6 +1124,7 @@ rnames = [ ( "{series} #{issue} - {title} {volume:02} ({year})", # Ensure format specifier works False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game 01 (2007).cbz", does_not_raise(), @@ -1120,6 +1132,7 @@ rnames = [ ( "{series} #{issue} - {title} ({year})({price})", # price should be none, test no space between ')(' False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1127,6 +1140,7 @@ rnames = [ ( "{series} #{issue} - {title} ({year}) ({price})", # price should be none, test double space ') (' False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1134,6 +1148,7 @@ rnames = [ ( "{series} #{issue} - {title} ({year})", False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1141,6 +1156,7 @@ rnames = [ ( "{title} {web_link}", # Ensure colon is replaced in metadata False, + False, "universal", "Anda's Game https---comicvine.gamespot.com-cory-doctorows-futuristic-tales-of-the-here-and-no-4000-140529-.cbz", does_not_raise(), @@ -1148,6 +1164,7 @@ rnames = [ ( "{title} {web_link}", # Ensure slashes are replaced in metadata on linux/macos False, + False, "Linux", "Anda's Game https:--comicvine.gamespot.com-cory-doctorows-futuristic-tales-of-the-here-and-no-4000-140529-.cbz", does_not_raise(), @@ -1155,6 +1172,7 @@ rnames = [ ( "{title} {web_links!j}", # Test that join forces str conversion False, + False, "Linux", "Anda's Game https:--comicvine.gamespot.com-cory-doctorows-futuristic-tales-of-the-here-and-no-4000-140529-.cbz", does_not_raise(), @@ -1162,6 +1180,7 @@ rnames = [ ( "{series}:{title} #{issue} ({year})", # on windows the ':' is replaced False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now-Anda's Game #001 (2007).cbz", does_not_raise(), @@ -1169,6 +1188,7 @@ rnames = [ ( "{series}: {title} #{issue} ({year})", # on windows the ':' is replaced False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007).cbz", does_not_raise(), @@ -1176,6 +1196,7 @@ rnames = [ ( "{series}: {title} #{issue} ({year})", # on linux the ':' is preserved False, + False, "Linux", "Cory Doctorow's Futuristic Tales of the Here and Now: Anda's Game #001 (2007).cbz", does_not_raise(), @@ -1183,6 +1204,7 @@ rnames = [ ( "{publisher}/ {series} #{issue} - {title} ({year})", # leading whitespace is removed when moving True, + False, "universal", "IDW Publishing/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1190,6 +1212,7 @@ rnames = [ ( "{publisher}/ {series} #{issue} - {title} ({year})", # leading whitespace is removed when only renaming False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1197,6 +1220,7 @@ rnames = [ ( r"{publisher}\ {series} #{issue} - {title} ({year})", # backslashes separate directories False, + False, "Linux", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1204,6 +1228,7 @@ rnames = [ ( "{series} # {issue} - {title} ({year})", # double spaces are reduced to one False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now # 001 - Anda's Game (2007).cbz", does_not_raise(), @@ -1211,6 +1236,7 @@ rnames = [ ( "{series} #{issue} - {locations!j} ({year})", False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - lonely cottage (2007).cbz", does_not_raise(), @@ -1218,6 +1244,7 @@ rnames = [ ( "{series} #{issue} - {title} - {WriteR}, {EDITOR} ({year})", # fields are case in-sensitive False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game - Dara Naraghi, Ted Adams (2007).cbz", does_not_raise(), @@ -1225,6 +1252,7 @@ rnames = [ ( "{series} v{price} #{issue} ({year})", # Remove previous text if value is "" False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 (2007).cbz", does_not_raise(), @@ -1232,6 +1260,7 @@ rnames = [ ( "{series} {price} #{issue} ({year})", # Ensure that a single space remains False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 (2007).cbz", does_not_raise(), @@ -1239,6 +1268,7 @@ rnames = [ ( "{series} - {title}{price} #{issue} ({year})", # Ensure removal before None values only impacts literal text False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007).cbz", does_not_raise(), @@ -1246,6 +1276,7 @@ rnames = [ ( "{series} - {title} {test} #{issue} ({year})", # Test non-existent key False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game {test} #001 (2007).cbz", does_not_raise(), @@ -1253,6 +1284,7 @@ rnames = [ ( "{series} - {title} #{issue} ({year} {price})", # Test null value in parenthesis with a non-null value False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007).cbz", does_not_raise(), @@ -1260,6 +1292,7 @@ rnames = [ ( "{series} - {title} #{issue} (of {price})", # null value with literal text in parenthesis False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001.cbz", does_not_raise(), @@ -1267,15 +1300,24 @@ rnames = [ ( "{series} - {title} {1} #{issue} ({year})", # Test numeric key False, + False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game {test} #001 (2007).cbz", pytest.raises(ValueError), ), + ( + "{series} - {title} #{issue} ({year})", + False, + True, + "universal", + "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007)/cory doctorow #1.cbz", + does_not_raise(), + ), ] -rfnames = [ - (None, lambda x: x.path.parent.absolute()), - ("", lambda x: pathlib.Path(os.getcwd())), - ("test", lambda x: (pathlib.Path(os.getcwd()) / "test")), - (pathlib.Path(os.getcwd()) / "test", lambda x: pathlib.Path(os.getcwd()) / "test"), +folder_names = [ + (None, lambda: pathlib.Path(str(cbz_path)).parent.absolute()), + ("", lambda: pathlib.Path(os.getcwd())), + ("test", lambda: (pathlib.Path(os.getcwd()) / "test")), + (pathlib.Path(os.getcwd()) / "test", lambda: pathlib.Path(os.getcwd()) / "test"), ] diff --git a/tests/rename_test.py b/tests/rename_test.py index ab71c82..cb3a3c2 100644 --- a/tests/rename_test.py +++ b/tests/rename_test.py @@ -6,18 +6,20 @@ import pytest from comicapi.genericmetadata import md_test from comictaggerlib import filerenamer -from testing.filenames import rfnames, rnames +from testing.filenames import file_renames, folder_names -@pytest.mark.parametrize("template, move, platform, expected, exception", rnames) -def test_rename(template, platform, move, expected, exception): - fr = filerenamer.FileRenamer(md_test, platform=platform) +@pytest.mark.parametrize("template, move, move_only, platform, expected, exception", file_renames) +def test_rename(template, move, move_only, platform, expected, exception): + fr = filerenamer.FileRenamer(None, platform=platform) + fr.set_metadata(md_test, "cory doctorow #1.cbz") fr.move = move + fr.move_only = move_only fr.set_template(template) with exception: assert str(pathlib.PureWindowsPath(fr.determine_name(".cbz"))) == str(pathlib.PureWindowsPath(expected)) -@pytest.mark.parametrize("inp, result", rfnames) +@pytest.mark.parametrize("inp, result", folder_names) def test_get_rename_dir(inp, result, cbz): - assert result(cbz) == filerenamer.get_rename_dir(cbz, inp) + assert result() == filerenamer.get_rename_dir(cbz, inp)