Improve rename

Implement rename on ComicArchive
Simplify unique_file with pathlib
Fix issues during renaming and simplify with pathlib
Allow exporting as zip to export 7-zip archives
This commit is contained in:
Timmy Welch 2022-07-09 22:27:45 -07:00
parent d24b51f94e
commit ccb461ae76
8 changed files with 110 additions and 107 deletions

View File

@ -746,7 +746,12 @@ class ComicArchive:
self.read_metadata(style)
def rename(self, path: pathlib.Path | str) -> None:
self.path = pathlib.Path(path)
new_path = pathlib.Path(path).absolute()
if new_path == self.path:
return
os.makedirs(new_path.parent, 0o777, True)
shutil.move(path, new_path)
self.path = new_path
self.archiver.path = pathlib.Path(path)
def sevenzip_test(self) -> bool:
@ -1247,10 +1252,10 @@ class ComicArchive:
return metadata
def export_as_zip(self, zipfilename: str) -> bool:
def export_as_zip(self, zip_filename: pathlib.Path | str) -> bool:
if self.archive_type == self.ArchiveType.Zip:
# nothing to do, we're already a zip
return True
zip_archiver = ZipArchiver(zipfilename)
zip_archiver = ZipArchiver(zip_filename)
return zip_archiver.copy_from_archive(self.archiver)

View File

@ -134,13 +134,13 @@ def sanitize_title(text: str, basic: bool = False) -> str:
return text
def unique_file(file_name: str) -> str:
def unique_file(file_name: pathlib.Path) -> pathlib.Path:
name = file_name.name
counter = 1
file_name_parts = os.path.splitext(file_name)
while True:
if not os.path.lexists(file_name):
if not file_name.exists():
return file_name
file_name = file_name_parts[0] + " (" + str(counter) + ")" + file_name_parts[1]
file_name = file_name.with_name(name + " (" + str(counter) + ")")
counter += 1

View File

@ -20,7 +20,7 @@ import argparse
import json
import logging
import os
import shutil
import pathlib
import sys
from pprint import pprint
@ -29,7 +29,7 @@ from comicapi.comicarchive import ComicArchive, MetaDataStyle
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.cbltransformer import CBLTransformer
from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
from comictaggerlib.filerenamer import FileRenamer
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
from comictaggerlib.issueidentifier import IssueIdentifier
from comictaggerlib.resulttypes import IssueResult, MultipleMatch, OnlineMatchResults
from comictaggerlib.settings import ComicTaggerSettings
@ -510,21 +510,18 @@ def process_file_cli(
)
return
folder = os.path.dirname(os.path.abspath(filename))
if settings.rename_move_dir and len(settings.rename_dir.strip()) > 3:
folder = settings.rename_dir.strip()
folder = get_rename_dir(ca, settings.rename_dir if settings.rename_move_dir else None)
new_abs_path = utils.unique_file(os.path.join(folder, new_name))
full_path = folder / new_name
if os.path.join(folder, new_name) == os.path.abspath(filename):
if full_path == ca.path:
print(msg_hdr + "Filename is already good!", file=sys.stderr)
return
suffix = ""
if not opts.dryrun:
# rename the file
os.makedirs(os.path.dirname(new_abs_path), 0o777, True)
shutil.move(filename, new_abs_path)
ca.rename(utils.unique_file(full_path))
else:
suffix = " (dry-run, no change)"
@ -535,18 +532,18 @@ def process_file_cli(
if batch_mode:
msg_hdr = f"{ca.path}: "
if not ca.is_rar():
logger.error(msg_hdr + "Archive is not a RAR.")
if ca.is_zip():
logger.error(msg_hdr + "Archive is already a zip file.")
return
rar_file = os.path.abspath(os.path.abspath(filename))
new_file = os.path.splitext(rar_file)[0] + ".cbz"
filename_path = pathlib.Path(filename).absolute()
new_file = filename_path.with_suffix(".cbz")
if opts.abort_on_conflict and os.path.lexists(new_file):
print(msg_hdr + f"{os.path.split(new_file)[1]} already exists in the that folder.")
if opts.abort_on_conflict and new_file.exists():
print(msg_hdr + f"{new_file.name} already exists in the that folder.")
return
new_file = utils.unique_file(os.path.join(new_file))
new_file = utils.unique_file(new_file)
delete_success = False
export_success = False
@ -555,16 +552,14 @@ def process_file_cli(
export_success = True
if opts.delete_after_zip_export:
try:
os.unlink(rar_file)
except OSError:
logger.exception(msg_hdr + "Error deleting original RAR after export")
delete_success = False
else:
filename_path.unlink(missing_ok=True)
delete_success = True
except OSError:
logger.exception(msg_hdr + "Error deleting original archive after export")
delete_success = False
else:
# last export failed, so remove the zip, if it exists
if os.path.lexists(new_file):
os.remove(new_file)
new_file.unlink(missing_ok=True)
else:
msg = msg_hdr + f"Dry-run: Would try to create {os.path.split(new_file)[1]}"
if opts.delete_after_zip_export:

View File

@ -25,12 +25,22 @@ from typing import Any, cast
from pathvalidate import sanitize_filename
from comicapi.comicarchive import ComicArchive
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
logger = logging.getLogger(__name__)
def get_rename_dir(ca: ComicArchive, rename_dir: str | pathlib.Path | None) -> pathlib.Path:
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()
return folder
class MetadataFormatter(string.Formatter):
def __init__(self, smart_cleanup: bool = False, platform: str = "auto") -> None:
super().__init__()

View File

@ -16,15 +16,13 @@
from __future__ import annotations
import logging
import os
import shutil
from typing import TypedDict
from PyQt5 import QtCore, QtWidgets, uic
from comicapi import utils
from comicapi.comicarchive import ComicArchive, MetaDataStyle
from comictaggerlib.filerenamer import FileRenamer
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
from comictaggerlib.settings import ComicTaggerSettings
from comictaggerlib.settingswindow import SettingsWindow
from comictaggerlib.ui.qtutils import center_window_on_parent
@ -32,11 +30,6 @@ from comictaggerlib.ui.qtutils import center_window_on_parent
logger = logging.getLogger(__name__)
class RenameItem(TypedDict):
archive: ComicArchive
new_name: str
class RenameWindow(QtWidgets.QDialog):
def __init__(
self,
@ -61,35 +54,28 @@ class RenameWindow(QtWidgets.QDialog):
self.settings = settings
self.comic_archive_list = comic_archive_list
self.data_style = data_style
self.rename_list: list[RenameItem] = []
self.rename_list: list[str] = []
self.btnSettings.clicked.connect(self.modify_settings)
self.renamer = FileRenamer(None, platform="universal" if self.settings.rename_strict else "auto")
self.config_renamer()
self.do_preview()
def config_renamer(self) -> None:
def config_renamer(self, ca: ComicArchive, md: GenericMetadata | None = None) -> str:
self.renamer.set_template(self.settings.rename_template)
self.renamer.set_issue_zero_padding(self.settings.rename_issue_number_padding)
self.renamer.set_smart_cleanup(self.settings.rename_use_smart_string_cleanup)
def do_preview(self) -> None:
self.twList.setRowCount(0)
self.twList.setSortingEnabled(False)
for ca in self.comic_archive_list:
new_ext = ca.path.suffix # default
if self.settings.rename_extension_based_on_archive:
if ca.is_sevenzip():
new_ext = ".cb7"
elif ca.is_zip():
new_ext = ".cbz"
elif ca.is_rar():
new_ext = ".cbr"
new_ext = ca.path.suffix # default
if self.settings.rename_extension_based_on_archive:
if ca.is_sevenzip():
new_ext = ".cb7"
elif ca.is_zip():
new_ext = ".cbz"
elif ca.is_rar():
new_ext = ".cbr"
if md is None:
md = ca.read_metadata(self.data_style)
if md.is_empty:
md = ca.metadata_from_filename(
@ -98,9 +84,17 @@ class RenameWindow(QtWidgets.QDialog):
self.settings.remove_fcbd,
self.settings.remove_publisher,
)
self.renamer.set_metadata(md)
self.renamer.move = self.settings.rename_move_dir
self.renamer.set_metadata(md)
self.renamer.move = self.settings.rename_move_dir
return new_ext
def do_preview(self) -> None:
self.twList.setRowCount(0)
self.twList.setSortingEnabled(False)
for ca in self.comic_archive_list:
new_ext = self.config_renamer(ca)
try:
new_name = self.renamer.determine_name(new_ext)
except Exception as e:
@ -122,13 +116,13 @@ class RenameWindow(QtWidgets.QDialog):
old_name_item = QtWidgets.QTableWidgetItem()
new_name_item = QtWidgets.QTableWidgetItem()
item_text = os.path.split(ca.path)[0]
item_text = str(ca.path.parent)
folder_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 0, folder_item)
folder_item.setText(item_text)
folder_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, item_text)
item_text = os.path.split(ca.path)[1]
item_text = str(ca.path.name)
old_name_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.twList.setItem(row, 1, old_name_item)
old_name_item.setText(item_text)
@ -139,13 +133,7 @@ class RenameWindow(QtWidgets.QDialog):
new_name_item.setText(new_name)
new_name_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, new_name)
dict_item = RenameItem(
{
"archive": ca,
"new_name": new_name,
}
)
self.rename_list.append(dict_item)
self.rename_list.append(new_name)
# Adjust column sizes
self.twList.setVisible(False)
@ -162,7 +150,6 @@ class RenameWindow(QtWidgets.QDialog):
settingswin.show_rename_tab()
settingswin.exec()
if settingswin.result():
self.config_renamer()
self.do_preview()
def accept(self) -> None:
@ -174,34 +161,29 @@ class RenameWindow(QtWidgets.QDialog):
center_window_on_parent(prog_dialog)
QtCore.QCoreApplication.processEvents()
for idx, item in enumerate(self.rename_list):
for idx, comic in enumerate(zip(self.comic_archive_list, self.rename_list)):
QtCore.QCoreApplication.processEvents()
if prog_dialog.wasCanceled():
break
idx += 1
prog_dialog.setValue(idx)
prog_dialog.setLabelText(item["new_name"])
prog_dialog.setLabelText(comic[1])
center_window_on_parent(prog_dialog)
QtCore.QCoreApplication.processEvents()
folder = os.path.dirname(os.path.abspath(item["archive"].path))
if self.settings.rename_move_dir and len(self.settings.rename_dir.strip()) > 3:
folder = self.settings.rename_dir.strip()
folder = get_rename_dir(comic[0], self.settings.rename_dir if self.settings.rename_move_dir else None)
new_abs_path = utils.unique_file(os.path.join(folder, item["new_name"]))
full_path = folder / comic[1]
if os.path.join(folder, item["new_name"]) == item["archive"].path:
logger.info(item["new_name"], "Filename is already good!")
if full_path == comic[0].path:
logger.info("%s: Filename is already good!", comic[1])
continue
if not item["archive"].is_writable(check_rar_status=False):
if not comic[0].is_writable(check_rar_status=False):
continue
os.makedirs(os.path.dirname(new_abs_path), 0o777, True)
shutil.move(item["archive"].path, new_abs_path)
item["archive"].rename(new_abs_path)
comic[0].rename(utils.unique_file(full_path))
prog_dialog.hide()
QtCore.QCoreApplication.processEvents()

View File

@ -18,6 +18,7 @@ from __future__ import annotations
import html
import logging
import os
import pathlib
import platform
from PyQt5 import QtCore, QtGui, QtWidgets, uic
@ -179,22 +180,29 @@ class SettingsWindow(QtWidgets.QDialog):
self.settings_to_form()
self.rename_error: Exception | None = None
self.rename_test()
self.dir_test()
self.btnBrowseRar.clicked.connect(self.select_rar)
self.btnClearCache.clicked.connect(self.clear_cache)
self.btnResetSettings.clicked.connect(self.reset_settings)
self.btnTestKey.clicked.connect(self.test_api_key)
self.btnTemplateHelp.clicked.connect(self.show_template_help)
self.leRenameTemplate.textEdited.connect(self.rename__test)
self.leRenameTemplate.textEdited.connect(self._rename_test)
self.cbxMoveFiles.clicked.connect(self.rename_test)
self.cbxMoveFiles.clicked.connect(self.dir_test)
self.cbxRenameStrict.clicked.connect(self.rename_test)
self.leDirectory.textEdited.connect(self.rename_test)
self.leDirectory.textEdited.connect(self.dir_test)
self.cbxComplicatedParser.clicked.connect(self.switch_parser)
def rename_test(self) -> None:
self.rename__test(self.leRenameTemplate.text())
self._rename_test(self.leRenameTemplate.text())
def rename__test(self, template: str) -> None:
def dir_test(self) -> None:
self.lblDir.setText(
str(pathlib.Path(self.leDirectory.text().strip()).absolute()) if self.cbxMoveFiles.isChecked() else ""
)
def _rename_test(self, template: str) -> None:
fr = FileRenamer(md_test, platform="universal" if self.cbxRenameStrict.isChecked() else "auto")
fr.move = self.cbxMoveFiles.isChecked()
fr.set_template(template)

View File

@ -446,16 +446,16 @@ Have fun!
def repackage_archive(self) -> None:
ca_list = self.fileSelectionList.get_selected_archive_list()
rar_count = 0
non_zip_count = 0
for ca in ca_list:
if ca.is_rar():
rar_count += 1
if not ca.is_zip():
non_zip_count += 1
if rar_count == 0:
if non_zip_count == 0:
QtWidgets.QMessageBox.information(
self, self.tr("Export as Zip Archive"), self.tr("No RAR archives selected!")
self, self.tr("Export as Zip Archive"), self.tr("Only ZIP archives are selected!")
)
logger.warning("Export as Zip Archive. No RAR archives selected")
logger.warning("Export as Zip Archive. Only ZIP archives are selected")
return
if not self.dirty_flag_verification(
@ -464,12 +464,12 @@ Have fun!
):
return
if rar_count != 0:
if non_zip_count != 0:
EW = ExportWindow(
self,
self.settings,
(
f"You have selected {rar_count} archive(s) to export to Zip format. "
f"You have selected {non_zip_count} archive(s) to export to Zip format. "
""" New archives will be created in the same folder as the original.
Please choose options below, and select OK.
@ -481,7 +481,7 @@ Have fun!
if not EW.exec():
return
prog_dialog = QtWidgets.QProgressDialog("", "Cancel", 0, rar_count, self)
prog_dialog = QtWidgets.QProgressDialog("", "Cancel", 0, non_zip_count, self)
prog_dialog.setWindowTitle("Exporting as ZIP")
prog_dialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
prog_dialog.setMinimumDuration(300)
@ -496,7 +496,7 @@ Have fun!
success_count = 0
for ca in ca_list:
if ca.is_rar():
if not ca.is_zip():
QtCore.QCoreApplication.processEvents()
if prog_dialog.wasCanceled():
break
@ -506,30 +506,30 @@ Have fun!
center_window_on_parent(prog_dialog)
QtCore.QCoreApplication.processEvents()
original_path = os.path.abspath(ca.path)
export_name = os.path.splitext(original_path)[0] + ".cbz"
export_name = ca.path.with_suffix(".cbz")
export = True
if os.path.lexists(export_name):
if export_name.exists():
if EW.fileConflictBehavior == ExportConflictOpts.dontCreate:
export_name = ""
export = False
skipped_list.append(ca.path)
elif EW.fileConflictBehavior == ExportConflictOpts.createUnique:
export_name = utils.unique_file(export_name)
if export_name:
if export:
if ca.export_as_zip(export_name):
success_count += 1
if EW.addToList:
new_archives_to_add.append(export_name)
new_archives_to_add.append(str(export_name))
if EW.deleteOriginal:
archives_to_remove.append(ca)
os.unlink(ca.path)
ca.path.unlink(missing_ok=True)
else:
# last export failed, so remove the zip, if it exists
failed_list.append(ca.path)
if os.path.lexists(export_name):
os.remove(export_name)
if export_name.exists():
export_name.unlink(missing_ok=True)
prog_dialog.hide()
QtCore.QCoreApplication.processEvents()
@ -539,13 +539,13 @@ Have fun!
summary = f"Successfully created {success_count} Zip archive(s)."
if len(skipped_list) > 0:
summary += (
f"\n\nThe following {len(skipped_list)} RAR archive(s) were skipped due to file name conflicts:\n"
f"\n\nThe following {len(skipped_list)} archive(s) were skipped due to file name conflicts:\n"
)
for f in skipped_list:
summary += f"\t{f}\n"
if len(failed_list) > 0:
summary += (
f"\n\nThe following {len(failed_list)} RAR archive(s) failed to export due to read/write errors:\n"
f"\n\nThe following {len(failed_list)} archive(s) failed to export due to read/write errors:\n"
)
for f in failed_list:
summary += f"\t{f}\n"

View File

@ -696,6 +696,9 @@ By default only removes restricted characters and filenames for the current Oper
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="lblDir"/>
</item>
</layout>
</item>
</layout>