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:
parent
d24b51f94e
commit
ccb461ae76
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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__()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user