diff --git a/comicapi/comet.py b/comicapi/comet.py index 04c22e1..4dd273a 100644 --- a/comicapi/comet.py +++ b/comicapi/comet.py @@ -14,11 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import xml.etree.ElementTree as ET from comicapi import utils from comicapi.genericmetadata import GenericMetadata +logger = logging.getLogger(__name__) + class CoMet: diff --git a/comicapi/comicarchive.py b/comicapi/comicarchive.py index 7aeec0c..022a9ac 100644 --- a/comicapi/comicarchive.py +++ b/comicapi/comicarchive.py @@ -47,6 +47,9 @@ from comicapi.filenameparser import FileNameParser from comicapi.genericmetadata import GenericMetadata, PageType logger = logging.getLogger(__name__) +if not pil_available: + logger.exception("PIL unavalable") + sys.path.insert(0, os.path.abspath(".")) @@ -77,10 +80,10 @@ class SevenZipArchiver: with py7zr.SevenZipFile(self.path, "r") as zf: data = zf.read(archive_file)[archive_file].read() except py7zr.Bad7zFile as e: - logger.waning("bad 7zip file [%s]: %s :: %s", e, self.path, archive_file) + logger.error("bad 7zip file [%s]: %s :: %s", e, self.path, archive_file) raise IOError except Exception as e: - logger.waning("bad 7zip file [%s]: %s :: %s", e, self.path, archive_file) + logger.error("bad 7zip file [%s]: %s :: %s", e, self.path, archive_file) raise IOError return data @@ -116,7 +119,7 @@ class SevenZipArchiver: return namelist except Exception as e: - logger.warning("Unable to get 7zip file list [%s]: %s", e, self.path) + logger.error("Unable to get 7zip file list [%s]: %s", e, self.path) return [] def rebuild_zip_file(self, exclude_list): @@ -134,8 +137,8 @@ class SevenZipArchiver: with py7zr.SevenZipFile(tmp_name, "w") as zout: for fname, bio in zin.read(targets).items(): zout.writef(bio, fname) - except Exception as e: - logger.warning("Exception[%s]: %s", e, self.path) + except Exception: + logger.exception("Error rebuilding 7zip file: %s", self.path) return [] # replace with the new file @@ -151,7 +154,7 @@ class SevenZipArchiver: if data is not None: zout.writestr(data, fname) except Exception as e: - logger.warning("Error while copying to %s: %s", self.path, e) + logger.exception("Error while copying to %s: %s", self.path, e) return False else: return True @@ -180,10 +183,10 @@ class ZipArchiver: try: data = zf.read(archive_file) except zipfile.BadZipfile as e: - logger.warning("bad zipfile [%s]: %s :: %s", e, self.path, archive_file) + logger.error("bad zipfile [%s]: %s :: %s", e, self.path, archive_file) raise IOError from e except Exception as e: - logger.warning("bad zipfile [%s]: %s :: %s", e, self.path, archive_file) + logger.error("bad zipfile [%s]: %s :: %s", e, self.path, archive_file) raise IOError from e return data @@ -209,7 +212,7 @@ class ZipArchiver: zf.writestr(archive_file, data) return True except Exception as e: - logger.warning("writing zip file failed [%s]: %s", e, self.path) + logger.error("writing zip file failed [%s]: %s", e, self.path) return False def get_filename_list(self): @@ -218,7 +221,7 @@ class ZipArchiver: namelist = zf.namelist() return namelist except Exception as e: - logger.warning("Unable to get zipfile list [%s]: %s", e, self.path) + logger.error("Unable to get zipfile list [%s]: %s", e, self.path) return [] def rebuild_zip_file(self, exclude_list): @@ -229,15 +232,19 @@ class ZipArchiver: tmp_fd, tmp_name = tempfile.mkstemp(dir=os.path.dirname(self.path)) os.close(tmp_fd) - with zipfile.ZipFile(self.path, "r") as zin: - with zipfile.ZipFile(tmp_name, "w", allowZip64=True) as zout: - for item in zin.infolist(): - buffer = zin.read(item.filename) - if item.filename not in exclude_list: - zout.writestr(item, buffer) + try: + with zipfile.ZipFile(self.path, "r") as zin: + with zipfile.ZipFile(tmp_name, "w", allowZip64=True) as zout: + for item in zin.infolist(): + buffer = zin.read(item.filename) + if item.filename not in exclude_list: + zout.writestr(item, buffer) - # preserve the old comment - zout.comment = zin.comment + # preserve the old comment + zout.comment = zin.comment + except Exception: + logger.exception("Error rebuilding 7zip file: %s", self.path) + return [] # replace with the new file os.remove(self.path) @@ -299,6 +306,7 @@ class ZipArchiver: else: raise Exception("Failed to write comment to zip file!") except Exception: + logger.exception() return False else: return True @@ -317,8 +325,8 @@ class ZipArchiver: if comment is not None: if not self.write_zip_comment(self.path, comment): return False - except Exception as e: - logger.warning("Error while copying to %s: %s", self.path, e) + except Exception: + logger.exception("Error while copying to %s", self.path) return False else: return True @@ -371,8 +379,8 @@ class RarArchiver: if platform.system() == "Darwin": time.sleep(1) os.remove(tmp_name) - except Exception as e: - logger.warning(e) + except Exception: + logger.exception("Failed to set a comment") return False else: return True @@ -401,10 +409,10 @@ class RarArchiver: ) continue except (OSError, IOError) as e: - logger.warning("read_file(): [%s] %s:%s attempt #%d", e, self.path, archive_file, tries) + logger.error("read_file(): [%s] %s:%s attempt #%d", e, self.path, archive_file, tries) time.sleep(1) except Exception as e: - logger.warning( + logger.error( "Unexpected exception in read_file(): [%s] for %s:%s attempt #%d", e, self.path, archive_file, tries ) break @@ -489,7 +497,7 @@ class RarArchiver: namelist.append(item.filename) except (OSError, IOError) as e: - logger.warning(f"get_filename_list(): [{e}] {self.path} attempt #{tries}".format(str(e), self.path, tries)) + logger.error(f"get_filename_list(): [{e}] {self.path} attempt #{tries}".format(str(e), self.path, tries)) time.sleep(1) else: @@ -505,7 +513,7 @@ class RarArchiver: rarc = rarfile.RarFile(self.path) except (OSError, IOError) as e: - logger.warning("getRARObj(): [%s] %s attempt #%s", e, self.path, tries) + logger.error("getRARObj(): [%s] %s attempt #%s", e, self.path, tries) time.sleep(1) else: @@ -537,7 +545,7 @@ class FolderArchiver: data = f.read() f.close() except IOError: - pass + logger.exception("Failed to read: %s", fname) return data @@ -549,6 +557,7 @@ class FolderArchiver: f.write(data) f.close() except: + logger.exception("Failed to read: %s", fname) return False else: return True @@ -559,6 +568,7 @@ class FolderArchiver: try: os.remove(fname) except: + logger.exception("Failed to read: %s", fname) return False else: return True @@ -787,7 +797,7 @@ class ComicArchive: try: image_data = self.archiver.read_file(filename) except IOError: - logger.warning("Error reading in page. Substituting logo page.") + logger.exception("Error reading in page. Substituting logo page.") image_data = ComicArchive.logo_data return image_data @@ -815,7 +825,7 @@ class ComicArchive: if count < 5: return None - # count the length of every filename, and count occurences + # count the length of every filename, and count occurrences length_buckets = {} for name in name_list: fname = os.path.split(name)[1] @@ -828,7 +838,7 @@ class ComicArchive: # sort by most common sorted_buckets = sorted(iter(length_buckets.items()), key=lambda k_v: (k_v[1], k_v[0]), reverse=True) - # statistical mode occurence is first + # statistical mode occurrence is first mode_length = sorted_buckets[0][0] # we are only going to consider the final image file: @@ -955,7 +965,7 @@ class ComicArchive: try: raw_cix = self.archiver.read_file(self.ci_xml_filename) except IOError as e: - logger.warning("Error reading in raw CIX!: %s", e) + logger.error("Error reading in raw CIX!: %s", e) raw_cix = "" return raw_cix @@ -1022,15 +1032,13 @@ class ComicArchive: def read_raw_comet(self): if not self.has_comet(): - err_msg = self.path + " doesn't have CoMet data!" - logger.info(err_msg) + logger.info("%s doesn't have CoMet data!", self.path) return None try: raw_comet = self.archiver.read_file(self.comet_filename) - except IOError as e: - err_msg = f"Error reading in raw CoMet!: {e}" - logger.warning(err_msg) + except: + logger.exception("Error reading in raw CoMet!") raw_comet = "" return raw_comet @@ -1110,7 +1118,8 @@ class ComicArchive: p["ImageSize"] = str(len(data)) p["ImageHeight"] = str(h) p["ImageWidth"] = str(w) - except IOError: + except Exception as e: + logger.warning("decoding image failed: %s", e) p["ImageSize"] = str(len(data)) else: diff --git a/comicapi/comicbookinfo.py b/comicapi/comicbookinfo.py index f408ac8..f24f896 100644 --- a/comicapi/comicbookinfo.py +++ b/comicapi/comicbookinfo.py @@ -15,12 +15,15 @@ # limitations under the License. import json +import logging from collections import defaultdict from datetime import datetime from comicapi import utils from comicapi.genericmetadata import GenericMetadata +logger = logging.getLogger(__name__) + class ComicBookInfo: def metadata_from_string(self, string): diff --git a/comicapi/comicinfoxml.py b/comicapi/comicinfoxml.py index 0079f2b..66a9407 100644 --- a/comicapi/comicinfoxml.py +++ b/comicapi/comicinfoxml.py @@ -14,12 +14,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import xml.etree.ElementTree as ET from comicapi import utils from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString +logger = logging.getLogger(__name__) + class ComicInfoXml: diff --git a/comicapi/filenameparser.py b/comicapi/filenameparser.py index 9e85ccd..f669a96 100644 --- a/comicapi/filenameparser.py +++ b/comicapi/filenameparser.py @@ -20,10 +20,13 @@ This should probably be re-written, but, well, it mostly works! # Some portions of this code were modified from pyComicMetaThis project # http://code.google.com/p/pycomicmetathis/ +import logging import os import re from urllib.parse import unquote +logger = logging.getLogger(__name__) + class FileNameParser: def __init__(self): diff --git a/comicapi/genericmetadata.py b/comicapi/genericmetadata.py index e375d7a..5bb47db 100644 --- a/comicapi/genericmetadata.py +++ b/comicapi/genericmetadata.py @@ -20,10 +20,13 @@ possible, however lossy it might be # See the License for the specific language governing permissions and # limitations under the License. +import logging from typing import List, TypedDict from comicapi import utils +logger = logging.getLogger(__name__) + class PageType: diff --git a/comicapi/issuestring.py b/comicapi/issuestring.py index 53f5a63..2d0eb86 100644 --- a/comicapi/issuestring.py +++ b/comicapi/issuestring.py @@ -20,6 +20,11 @@ comics industry throws at us. # limitations under the License. +import logging + +logger = logging.getLogger(__name__) + + class IssueString: def __init__(self, text): diff --git a/comicapi/utils.py b/comicapi/utils.py index e88bc4e..8dbfa66 100644 --- a/comicapi/utils.py +++ b/comicapi/utils.py @@ -16,6 +16,7 @@ import codecs import locale +import logging import os import platform import re @@ -25,6 +26,8 @@ from collections import defaultdict import pycountry +logger = logging.getLogger(__name__) + class UtilsVars: already_fixed_encoding = False diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py index 234efcd..debdcf4 100644 --- a/comictaggerlib/autotagmatchwindow.py +++ b/comictaggerlib/autotagmatchwindow.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os from typing import List, Optional @@ -25,6 +26,8 @@ from comictaggerlib.resulttypes import MultipleMatch from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class AutoTagMatchWindow(QtWidgets.QDialog): volume_id = 0 diff --git a/comictaggerlib/autotagprogresswindow.py b/comictaggerlib/autotagprogresswindow.py index bf982ba..53c6ab3 100644 --- a/comictaggerlib/autotagprogresswindow.py +++ b/comictaggerlib/autotagprogresswindow.py @@ -15,12 +15,16 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets, uic from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class AutoTagProgressWindow(QtWidgets.QDialog): def __init__(self, parent): diff --git a/comictaggerlib/autotagstartwindow.py b/comictaggerlib/autotagstartwindow.py index d7d421c..3a7b8dc 100644 --- a/comictaggerlib/autotagstartwindow.py +++ b/comictaggerlib/autotagstartwindow.py @@ -15,10 +15,14 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtGui, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class AutoTagStartWindow(QtWidgets.QDialog): def __init__(self, parent, settings, msg): diff --git a/comictaggerlib/cbltransformer.py b/comictaggerlib/cbltransformer.py index 47cc1f4..f5242ed 100644 --- a/comictaggerlib/cbltransformer.py +++ b/comictaggerlib/cbltransformer.py @@ -13,8 +13,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import logging + from comicapi.genericmetadata import GenericMetadata +logger = logging.getLogger(__name__) + class CBLTransformer: def __init__(self, metadata: GenericMetadata, settings): diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py index 6c6f38b..a386095 100644 --- a/comictaggerlib/cli.py +++ b/comictaggerlib/cli.py @@ -17,6 +17,7 @@ # limitations under the License. import json +import logging import os import sys from pprint import pprint @@ -32,6 +33,7 @@ from comictaggerlib.resulttypes import MultipleMatch, OnlineMatchResults from comictaggerlib.settings import ComicTaggerSettings filename_encoding = sys.getfilesystemencoding() +logger = logging.getLogger(__name__) def actual_issue_data_fetch(match, settings, opts): @@ -41,7 +43,7 @@ def actual_issue_data_fetch(match, settings, opts): comic_vine.wait_for_rate_limit = opts.wait_and_retry_on_rate_limit cv_md = comic_vine.fetch_issue_data(match["volume_id"], match["issue_number"], settings) except ComicVineTalkerException: - print("Network error while getting issue details. Save aborted", file=sys.stderr) + logger.exception("Network error while getting issue details. Save aborted") return None if settings.apply_cbl_transform_on_cv_import: @@ -54,15 +56,18 @@ def actual_metadata_save(ca: ComicArchive, opts, md): if not opts.dryrun: # write out the new data if not ca.write_metadata(md, opts.data_style): - print("The tag save seemed to fail!", file=sys.stderr) + logger.error("The tag save seemed to fail!") return False - print("Save complete.", file=sys.stderr) + print("Save complete.") + logger.info("Save complete.") else: if opts.terse: - print("dry-run option was set, so nothing was written", file=sys.stderr) + logger.info("dry-run option was set, so nothing was written") + print("dry-run option was set, so nothing was written") else: - print("dry-run option was set, so nothing was written, but here is the final set of tags:", file=sys.stderr) + logger.info("dry-run option was set, so nothing was written, but here is the final set of tags:") + print("dry-run option was set, so nothing was written, but here is the final set of tags:") print(f"{md}") return True @@ -147,7 +152,7 @@ def post_process_matches(match_results: OnlineMatchResults, opts, settings): def cli_mode(opts, settings): if len(opts.file_list) < 1: - print("You must specify at least one filename. Use the -h option for more info", file=sys.stderr) + logger.error("You must specify at least one filename. Use the -h option for more info") return match_results = OnlineMatchResults() @@ -183,15 +188,15 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults ca = ComicArchive(filename, settings.rar_exe_path, ComicTaggerSettings.get_graphic("nocover.png")) if not os.path.lexists(filename): - print("Cannot find " + filename, file=sys.stderr) + logger.error("Cannot find " + filename) return if not ca.seems_to_be_a_comic_archive(): - print(f"Sorry, but {filename} is not a comic archive!", file=sys.stderr) + logger.error("Sorry, but %s is not a comic archive!", filename) return if not ca.is_writable() and (opts.delete_tags or opts.copy_tags or opts.save_tags or opts.rename_file): - print("This archive is not writable for that tag type", file=sys.stderr) + logger.error("This archive is not writable for that tag type") return has = [False, False, False] @@ -328,12 +333,12 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults comic_vine.wait_for_rate_limit = opts.wait_and_retry_on_rate_limit cv_md = comic_vine.fetch_issue_data_by_issue_id(opts.issue_id, settings) except ComicVineTalkerException: - print("Network error while getting issue details. Save aborted", file=sys.stderr) + logger.exception("Network error while getting issue details. Save aborted") match_results.fetch_data_failures.append(ca.path) return if cv_md is None: - print(f"No match for ID {opts.issue_id} was found.", file=sys.stderr) + logger.error("No match for ID %s was found.", opts.issue_id) match_results.no_matches.append(ca.path) return @@ -343,7 +348,7 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults ii = IssueIdentifier(ca, settings) if md is None or md.is_empty: - print("No metadata given to search online with!", file=sys.stderr) + logger.error("No metadata given to search online with!") match_results.no_matches.append(ca.path) return @@ -382,19 +387,19 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults if choices: if low_confidence: - print("Online search: Multiple low confidence matches. Save aborted", file=sys.stderr) + logger.error("Online search: Multiple low confidence matches. Save aborted") match_results.low_confidence_matches.append(MultipleMatch(ca, matches)) return - print("Online search: Multiple good matches. Save aborted", file=sys.stderr) + logger.error("Online search: Multiple good matches. Save aborted") match_results.multiple_matches.append(MultipleMatch(ca, matches)) return if low_confidence and opts.abortOnLowConfidence: - print("Online search: Low confidence match. Save aborted", file=sys.stderr) + logger.error("Online search: Low confidence match. Save aborted") match_results.low_confidence_matches.append(MultipleMatch(ca, matches)) return if not found_match: - print("Online search: No match found. Save aborted", file=sys.stderr) + logger.error("Online search: No match found. Save aborted") match_results.no_matches.append(ca.path) return @@ -428,7 +433,7 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults md = create_local_metadata(opts, ca, use_tags) if md.series is None: - print(msg_hdr + "Can't rename without series name", file=sys.stderr) + logger.error(msg_hdr + "Can't rename without series name") return new_ext = None # default @@ -448,7 +453,7 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults new_name = renamer.determine_name(ca.path, ext=new_ext) if new_name == os.path.basename(ca.path): - print(msg_hdr + "Filename is already good!", file=sys.stderr) + logger.error(msg_hdr + "Filename is already good!") return folder = os.path.dirname(os.path.abspath(ca.path)) @@ -469,7 +474,7 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults msg_hdr = f"{ca.path}: " if not ca.is_rar(): - print(msg_hdr + "Archive is not a RAR.", file=sys.stderr) + logger.error(msg_hdr + "Archive is not a RAR.") return rar_file = os.path.abspath(os.path.abspath(filename)) @@ -490,7 +495,7 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults try: os.unlink(rar_file) except: - print(msg_hdr + "Error deleting original RAR after export", file=sys.stderr) + logger.exception(msg_hdr + "Error deleting original RAR after export") delete_success = False else: delete_success = True diff --git a/comictaggerlib/comicvinecacher.py b/comictaggerlib/comicvinecacher.py index b26ffad..2106aad 100644 --- a/comictaggerlib/comicvinecacher.py +++ b/comictaggerlib/comicvinecacher.py @@ -15,6 +15,7 @@ # limitations under the License. import datetime +import logging import os import sqlite3 as lite @@ -22,6 +23,8 @@ from comicapi import utils from comictaggerlib import ctversion from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class ComicVineCacher: def __init__(self): diff --git a/comictaggerlib/comicvinetalker.py b/comictaggerlib/comicvinetalker.py index fe6aa26..6088d1b 100644 --- a/comictaggerlib/comicvinetalker.py +++ b/comictaggerlib/comicvinetalker.py @@ -15,6 +15,7 @@ # limitations under the License. import json +import logging import re import sys import time @@ -30,6 +31,8 @@ from comicapi.issuestring import IssueString from comictaggerlib import ctversion from comictaggerlib.comicvinecacher import ComicVineCacher +logger = logging.getLogger(__name__) + try: from PyQt5 import QtCore, QtNetwork @@ -37,6 +40,8 @@ try: except ImportError: qt_available = False +logger = logging.getLogger(__name__) + class SelectDetails(TypedDict): image_url: str @@ -115,7 +120,7 @@ class ComicVineTalker: def write_log(self, text): if self.log_func is None: - print(text, file=sys.stderr) + logger.info(text, file=sys.stderr) else: self.log_func(text) @@ -599,7 +604,7 @@ class ComicVineTalker: except: # we caught an error rebuilding the table. # just bail and remove the formatting - print("table parse error") + logger.exception("table parse error") newstring.replace("{}", "") return newstring @@ -743,12 +748,11 @@ class ComicVineTalker: try: cv_response = json.loads(bytes(data)) except Exception: - print("Comic Vine query failed to get JSON data", file=sys.stderr) - print(str(data), file=sys.stderr) + logger.exception("Comic Vine query failed to get JSON data\n%s", str(data)) return if cv_response["status_code"] != 1: - print("Comic Vine query failed with error: [{0}]. ".format(cv_response["error"]), file=sys.stderr) + logger.error("Comic Vine query failed with error: [%s]. ", cv_response["error"]) return image_url = cv_response["results"]["image"]["super_url"] diff --git a/comictaggerlib/coverimagewidget.py b/comictaggerlib/coverimagewidget.py index d2d08dd..285e46d 100644 --- a/comictaggerlib/coverimagewidget.py +++ b/comictaggerlib/coverimagewidget.py @@ -19,6 +19,8 @@ TODO: This should be re-factored using subclasses! # limitations under the License. +import logging + from PyQt5 import QtCore, QtGui, QtWidgets, uic from comicapi.comicarchive import ComicArchive @@ -29,6 +31,8 @@ from comictaggerlib.pageloader import PageLoader from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import get_qimage_from_data, reduce_widget_font_size +logger = logging.getLogger(__name__) + def clickable(widget): """Allow a label to be clickable""" diff --git a/comictaggerlib/crediteditorwindow.py b/comictaggerlib/crediteditorwindow.py index 8dd7df8..380390f 100644 --- a/comictaggerlib/crediteditorwindow.py +++ b/comictaggerlib/crediteditorwindow.py @@ -15,10 +15,14 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class CreditEditorWindow(QtWidgets.QDialog): ModeEdit = 0 diff --git a/comictaggerlib/exportwindow.py b/comictaggerlib/exportwindow.py index 6f2a1cf..4b5f728 100644 --- a/comictaggerlib/exportwindow.py +++ b/comictaggerlib/exportwindow.py @@ -15,10 +15,14 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class ExportConflictOpts: dontCreate = 1 diff --git a/comictaggerlib/filerenamer.py b/comictaggerlib/filerenamer.py index 4d46623..e35aa63 100644 --- a/comictaggerlib/filerenamer.py +++ b/comictaggerlib/filerenamer.py @@ -15,12 +15,15 @@ # limitations under the License. import datetime +import logging import os import re from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString +logger = logging.getLogger(__name__) + class FileRenamer: def __init__(self, metadata): diff --git a/comictaggerlib/fileselectionlist.py b/comictaggerlib/fileselectionlist.py index b4c5a70..00154fe 100644 --- a/comictaggerlib/fileselectionlist.py +++ b/comictaggerlib/fileselectionlist.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os from typing import List @@ -24,6 +25,8 @@ from comicapi.comicarchive import ComicArchive from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import center_window_on_parent, reduce_widget_font_size +logger = logging.getLogger(__name__) + class FileTableWidgetItem(QtWidgets.QTableWidgetItem): def __lt__(self, other): diff --git a/comictaggerlib/imagefetcher.py b/comictaggerlib/imagefetcher.py index c19758d..a799ce7 100644 --- a/comictaggerlib/imagefetcher.py +++ b/comictaggerlib/imagefetcher.py @@ -30,9 +30,13 @@ except ImportError: qt_available = False +import logging + from comictaggerlib import ctversion from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class ImageFetcherException(Exception): pass @@ -81,10 +85,9 @@ class ImageFetcher: if blocking or not qt_available: if image_data is None: try: - print(url) image_data = requests.get(url, headers={"user-agent": "comictagger/" + ctversion.version}).content except Exception as e: - print(e) + logger.exception("Fetching url failed: %s") raise ImageFetcherException("Network Error!") from e # save the image to the cache @@ -106,7 +109,7 @@ class ImageFetcher: def finish_request(self, reply): # read in the image data - print("request finished") + logger.debug("request finished") image_data = reply.readAll() # save the image to the cache diff --git a/comictaggerlib/imagehasher.py b/comictaggerlib/imagehasher.py index 7cd9a1b..ddeb65d 100755 --- a/comictaggerlib/imagehasher.py +++ b/comictaggerlib/imagehasher.py @@ -15,6 +15,7 @@ # limitations under the License. import io +import logging from functools import reduce try: @@ -23,6 +24,7 @@ try: pil_available = True except ImportError: pil_available = False +logger = logging.getLogger(__name__) class ImageHasher: @@ -38,16 +40,16 @@ class ImageHasher: self.image = Image.open(path) else: self.image = Image.open(io.BytesIO(data)) - except Exception as e: - print(f"Image data seems corrupted! [{e}]") + except Exception: + logger.exception("Image data seems corrupted!") # just generate a bogus image self.image = Image.new("L", (1, 1)) def average_hash(self): try: image = self.image.resize((self.width, self.height), Image.ANTIALIAS).convert("L") - except Exception as e: - print("average_hash error:", e) + except Exception: + logger.exception("average_hash error") return int(0) pixels = list(image.getdata()) diff --git a/comictaggerlib/imagepopup.py b/comictaggerlib/imagepopup.py index 1f31724..06a8ddc 100644 --- a/comictaggerlib/imagepopup.py +++ b/comictaggerlib/imagepopup.py @@ -15,10 +15,14 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtGui, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class ImagePopup(QtWidgets.QDialog): def __init__(self, parent, image_pixmap): diff --git a/comictaggerlib/issueidentifier.py b/comictaggerlib/issueidentifier.py index c833cfa..03997b5 100644 --- a/comictaggerlib/issueidentifier.py +++ b/comictaggerlib/issueidentifier.py @@ -15,6 +15,7 @@ # limitations under the License. import io +import logging import sys from typing import List, TypedDict @@ -27,6 +28,8 @@ from comictaggerlib.imagefetcher import ImageFetcher, ImageFetcherException from comictaggerlib.imagehasher import ImageHasher from comictaggerlib.resulttypes import IssueResult +logger = logging.getLogger(__name__) + try: from PIL import Image @@ -141,8 +144,8 @@ class IssueIdentifier: try: cropped_im = im.crop((int(w / 2), 0, w, h)) - except Exception as e: - print("cropCover() error:", e) + except: + logger.exception("cropCover() error") return None output = io.BytesIO() diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py index 4123c7e..69dd136 100644 --- a/comictaggerlib/issueselectionwindow.py +++ b/comictaggerlib/issueselectionwindow.py @@ -15,6 +15,8 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtGui, QtWidgets, uic from comicapi.issuestring import IssueString @@ -23,6 +25,8 @@ from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class IssueNumberTableWidgetItem(QtWidgets.QTableWidgetItem): def __lt__(self, other): diff --git a/comictaggerlib/logwindow.py b/comictaggerlib/logwindow.py index 9cea351..eb24397 100644 --- a/comictaggerlib/logwindow.py +++ b/comictaggerlib/logwindow.py @@ -15,10 +15,14 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class LogWindow(QtWidgets.QDialog): def __init__(self, parent): diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 0f76f6e..289dc56 100755 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -14,28 +14,58 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging +import logging.handlers import os +import pathlib import platform import signal import sys import traceback +import pkg_resources + from comictaggerlib import cli from comictaggerlib.comicvinetalker import ComicVineTalker +from comictaggerlib.ctversion import version from comictaggerlib.options import Options from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger("comictagger") +logging.getLogger("comicapi").setLevel(logging.DEBUG) +logger.setLevel(logging.DEBUG) + try: qt_available = True from PyQt5 import QtGui, QtWidgets from comictaggerlib.taggerwindow import TaggerWindow except ImportError as e: - print(e) + logging.debug(e) qt_available = False +def rotate(handler: logging.handlers.RotatingFileHandler, filename: pathlib.Path): + if filename.is_file() and filename.stat().st_size > 0: + handler.doRollover() + + def ctmain(): + stream_handler = logging.StreamHandler() + stream_handler.setLevel(logging.WARNING) + file_handler = logging.handlers.RotatingFileHandler( + ComicTaggerSettings.get_settings_folder() / "logs" / "ComicTagger.log", encoding="utf-8", backupCount=10 + ) + rotate(file_handler, ComicTaggerSettings.get_settings_folder() / "logs" / "ComicTagger.log") + logging.basicConfig( + handlers=[ + stream_handler, + file_handler, + ], + level=logging.WARNING, + format="%(asctime)s | %(name)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%dT%H:%M:%S", + ) opts = Options() opts.parse_cmd_line_args() @@ -55,12 +85,27 @@ def ctmain(): signal.signal(signal.SIGINT, signal.SIG_DFL) + logger.info( + "ComicTagger Version: %s running on: %s PyInstaller: %s", + version, + platform.system(), + "Yes" if getattr(sys, "frozen", None) else "No", + ) + + logger.debug("Installed Packages") + for pkg in sorted(pkg_resources.working_set, key=lambda x: x.project_name): + logger.debug("%s\t%s", pkg.project_name, pkg.version) + if not qt_available and not opts.no_gui: opts.no_gui = True - print("PyQt5 is not available. ComicTagger is limited to command-line mode.", file=sys.stderr) + print("PyQt5 is not available. ComicTagger is limited to command-line mode.") + logger.info("PyQt5 is not available. ComicTagger is limited to command-line mode.") if opts.no_gui: - cli.cli_mode(opts, SETTINGS) + try: + cli.cli_mode(opts, SETTINGS) + except: + logger.exception() else: os.environ["QtWidgets.QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" args = [] @@ -103,7 +148,7 @@ def ctmain(): sys.exit(app.exec()) except Exception: - print(traceback.format_exc()) + logger.exception() QtWidgets.QMessageBox.critical( QtWidgets.QMainWindow(), "Error", "Unhandled exception in app:\n" + traceback.format_exc() ) diff --git a/comictaggerlib/matchselectionwindow.py b/comictaggerlib/matchselectionwindow.py index a601153..a24b909 100644 --- a/comictaggerlib/matchselectionwindow.py +++ b/comictaggerlib/matchselectionwindow.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os from PyQt5 import QtCore, QtWidgets, uic @@ -22,6 +23,8 @@ from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class MatchSelectionWindow(QtWidgets.QDialog): volume_id = 0 diff --git a/comictaggerlib/optionalmsgdialog.py b/comictaggerlib/optionalmsgdialog.py index 6456dd7..b9ff077 100644 --- a/comictaggerlib/optionalmsgdialog.py +++ b/comictaggerlib/optionalmsgdialog.py @@ -25,8 +25,12 @@ said_yes, checked = OptionalMessageDialog.question(self, "QtWidgets.Question", # See the License for the specific language governing permissions and # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets +logger = logging.getLogger(__name__) + StyleMessage = 0 StyleQuestion = 1 diff --git a/comictaggerlib/options.py b/comictaggerlib/options.py index dfdbbe5..a19fcf0 100644 --- a/comictaggerlib/options.py +++ b/comictaggerlib/options.py @@ -15,17 +15,18 @@ # limitations under the License. import getopt +import logging import os import platform import sys -import traceback -from datetime import datetime from comicapi import utils from comicapi.comicarchive import MetaDataStyle from comicapi.genericmetadata import GenericMetadata from comictaggerlib import ctversion +logger = logging.getLogger(__name__) + class Options: help_text = """Usage: {0} [option] ... [file [files ...]] @@ -192,7 +193,7 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki # Map the dict to the metadata object for key, value in md_dict.items(): if not hasattr(md, key): - print(f"Warning: '{key}' is not a valid tag name") + logger.warning("'%s' is not a valid tag name", key) else: md.is_empty = False setattr(md, key, value) @@ -210,7 +211,7 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki break sys.argv = script_args if not os.path.exists(scriptfile): - print(f"Can't find {scriptfile}") + logger.error("Can't find %s", scriptfile) else: # I *think* this makes sense: # assume the base name of the file is the module name @@ -225,10 +226,9 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki if "main" in dir(script): script.main() else: - print(f"Can't find entry point 'main()' in module '{module_name}'") - except Exception as e: - print("Script raised an unhandled exception: ", e) - print(traceback.format_exc()) + logger.error("Can't find entry point 'main()' in module '%s'", module_name) + except Exception: + logger.exception("Script: %s raised an unhandled exception: ", module_name) sys.exit(0) @@ -360,7 +360,7 @@ For more help visit the wiki at: https://github.com/comictagger/comictagger/wiki if o == "--only-set-cv-key": self.only_set_key = True if o == "--version": - print(f"ComicTagger {ctversion.version}: Copyright (c) 2012-{datetime.today():%Y} ComicTagger Team") + print(f"ComicTagger {ctversion.version}: Copyright (c) 2012-2022 ComicTagger Team") print("Distributed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)") sys.exit(0) if o in ("-t", "--type"): diff --git a/comictaggerlib/pagebrowser.py b/comictaggerlib/pagebrowser.py index 9b9f554..989e438 100644 --- a/comictaggerlib/pagebrowser.py +++ b/comictaggerlib/pagebrowser.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import platform from PyQt5 import QtCore, QtGui, QtWidgets, uic @@ -22,6 +23,8 @@ from comicapi.comicarchive import ComicArchive from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + class PageBrowserWindow(QtWidgets.QDialog): def __init__(self, parent, metadata): diff --git a/comictaggerlib/pagelisteditor.py b/comictaggerlib/pagelisteditor.py index 45d8c44..0c2838d 100644 --- a/comictaggerlib/pagelisteditor.py +++ b/comictaggerlib/pagelisteditor.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging from typing import Optional from PyQt5 import QtCore, QtGui, QtWidgets, uic @@ -23,6 +24,8 @@ from comicapi.genericmetadata import PageType from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + def item_move_events(widget): class Filter(QtCore.QObject): diff --git a/comictaggerlib/pageloader.py b/comictaggerlib/pageloader.py index 832f138..3843dc0 100644 --- a/comictaggerlib/pageloader.py +++ b/comictaggerlib/pageloader.py @@ -14,10 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging + from PyQt5 import QtCore from comicapi.comicarchive import ComicArchive +logger = logging.getLogger(__name__) + class PageLoader(QtCore.QThread): """ diff --git a/comictaggerlib/progresswindow.py b/comictaggerlib/progresswindow.py index 26605e4..88e95e3 100644 --- a/comictaggerlib/progresswindow.py +++ b/comictaggerlib/progresswindow.py @@ -15,11 +15,15 @@ # limitations under the License. +import logging + from PyQt5 import QtCore, QtWidgets, uic from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class IDProgressWindow(QtWidgets.QDialog): def __init__(self, parent): diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py index 8e34ab8..bb02a84 100644 --- a/comictaggerlib/renamewindow.py +++ b/comictaggerlib/renamewindow.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os from typing import List @@ -27,6 +28,8 @@ from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.settingswindow import SettingsWindow from comictaggerlib.ui.qtutils import center_window_on_parent +logger = logging.getLogger(__name__) + class RenameWindow(QtWidgets.QDialog): def __init__(self, parent, comic_archive_list: List[comicapi.comicarchive.ComicArchive], data_style, settings): @@ -149,6 +152,7 @@ class RenameWindow(QtWidgets.QDialog): if item["new_name"] == os.path.basename(item["archive"].path): print(item["new_name"], "Filename is already good!") + logger.info(item["new_name"], "Filename is already good!") continue if not item["archive"].is_writable(check_rar_status=False): diff --git a/comictaggerlib/settings.py b/comictaggerlib/settings.py index 709d642..fefdd16 100644 --- a/comictaggerlib/settings.py +++ b/comictaggerlib/settings.py @@ -15,13 +15,17 @@ # limitations under the License. import configparser +import logging import os +import pathlib import platform import sys import uuid from comicapi import utils +logger = logging.getLogger(__name__) + class ComicTaggerSettings: @staticmethod @@ -30,18 +34,18 @@ class ComicTaggerSettings: folder = os.path.join(os.environ["APPDATA"], "ComicTagger") else: folder = os.path.join(os.path.expanduser("~"), ".ComicTagger") - return folder + return pathlib.Path(folder) @staticmethod def base_dir(): if getattr(sys, "frozen", None): return sys._MEIPASS - return os.path.dirname(os.path.abspath(__file__)) + return pathlib.Path(__file__).parent @staticmethod def get_graphic(filename): - graphic_folder = os.path.join(ComicTaggerSettings.base_dir(), "graphics") + graphic_folder = pathlib.Path(os.path.join(ComicTaggerSettings.base_dir(), "graphics")) return os.path.join(graphic_folder, filename) @staticmethod diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 5746b34..904ee36 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os import platform @@ -25,6 +26,8 @@ from comictaggerlib.comicvinetalker import ComicVineTalker from comictaggerlib.imagefetcher import ImageFetcher from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + windowsRarHelp = """

To write to CBR/RAR archives, you will need to have the tools from diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 3f6d6e6..0ea91d5 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -15,6 +15,7 @@ # limitations under the License. import json +import logging import operator import os import pickle @@ -58,6 +59,8 @@ from comictaggerlib.ui.qtutils import center_window_on_parent, reduce_widget_fon from comictaggerlib.versionchecker import VersionChecker from comictaggerlib.volumeselectionwindow import VolumeSelectionWindow +logger = logging.getLogger(__name__) + def execute(f: callable): f() @@ -79,6 +82,7 @@ class TaggerWindow(QtWidgets.QMainWindow): alive = socket.waitForConnected(3000) if alive: print(f"Another application with key [{settings.install_id}] is already running") + logger.info(f"Another application with key [{settings.install_id}] is already running") # send file list to other instance if file_list: socket.write(pickle.dumps(file_list)) @@ -96,8 +100,10 @@ class TaggerWindow(QtWidgets.QMainWindow): self.socketServer.removeServer(settings.install_id) ok = self.socketServer.listen(settings.install_id) if not ok: - print( - f"Cannot start local socket with key [{settings.install_id}]. Reason: {self.socketServer.errorString()}" + logger.error( + "Cannot start local socket with key [%s]. Reason: %s", + settings.install_id, + self.socketServer.errorString(), ) sys.exit() @@ -404,6 +410,7 @@ Have fun! QtWidgets.QMessageBox.information( self, self.tr("Export as Zip Archive"), self.tr("No RAR archives selected!") ) + logger.warning("Export as Zip Archive. No RAR archives selected") return if not self.dirty_flag_verification( @@ -496,6 +503,7 @@ Please choose options below, and select OK. for f in failed_list: summary += f"\t{f}\n" + logger.info(summary) dlg = LogWindow(self) dlg.set_text(summary) dlg.setWindowTitle("Archive Export to Zip Summary") @@ -1050,7 +1058,6 @@ Please choose options below, and select OK. "Change Tag Read Style", "If you change read tag style now, data in the form will be lost. Are you sure?" ): self.load_data_style = self.cbLoadDataStyle.itemData(s) - print("load style:", self.load_data_style) self.settings.last_selected_load_data_style = self.load_data_style self.update_menus() if self.comic_archive is not None: @@ -1062,7 +1069,6 @@ Please choose options below, and select OK. def set_save_data_style(self, s): self.save_data_style = self.cbSaveDataStyle.itemData(s) - self.settings.last_selected_save_data_style = self.save_data_style self.update_style_tweaks() self.update_menus() @@ -1603,7 +1609,7 @@ Please choose options below, and select OK. comic_vine.wait_for_rate_limit = self.settings.wait_and_retry_on_rate_limit cv_md = comic_vine.fetch_issue_data(match["volume_id"], match["issue_number"], self.settings) except ComicVineTalkerException: - print("Network error while getting issue details. Save aborted") + logger.exception("Network error while getting issue details. Save aborted") if cv_md is not None: if self.settings.apply_cbl_transform_on_cv_import: @@ -1641,7 +1647,7 @@ Please choose options below, and select OK. md.series = dlg.search_string if md is None or md.is_empty: - print("No metadata given to search online with!") + logger.error("No metadata given to search online with!") return False, match_results if dlg.dont_use_year: @@ -1831,6 +1837,7 @@ Please choose options below, and select OK to Auto-Tag. else: QtWidgets.QMessageBox.information(self, self.tr("Auto-Tag Summary"), self.tr(summary)) + logger.info(summary) def dirty_flag_verification(self, title, desc): if self.dirty_flag: diff --git a/comictaggerlib/ui/qtutils.py b/comictaggerlib/ui/qtutils.py index eb74ae2..e9d82c1 100644 --- a/comictaggerlib/ui/qtutils.py +++ b/comictaggerlib/ui/qtutils.py @@ -1,9 +1,12 @@ """Some utilities for the GUI""" import io +import logging from comictaggerlib.settings import ComicTaggerSettings +logger = logging.getLogger(__name__) + try: from PyQt5 import QtGui diff --git a/comictaggerlib/versionchecker.py b/comictaggerlib/versionchecker.py index abce630..37e381a 100644 --- a/comictaggerlib/versionchecker.py +++ b/comictaggerlib/versionchecker.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import platform import sys @@ -21,6 +22,8 @@ import requests from comictaggerlib import ctversion +logger = logging.getLogger(__name__) + class VersionChecker: def get_request_url(self, uuid, use_stats): diff --git a/comictaggerlib/volumeselectionwindow.py b/comictaggerlib/volumeselectionwindow.py index 2fd2a25..79ce9e4 100644 --- a/comictaggerlib/volumeselectionwindow.py +++ b/comictaggerlib/volumeselectionwindow.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging from PyQt5 import QtCore, QtWidgets, uic from PyQt5.QtCore import pyqtSignal @@ -29,6 +30,8 @@ from comictaggerlib.progresswindow import IDProgressWindow from comictaggerlib.settings import ComicTaggerSettings from comictaggerlib.ui.qtutils import reduce_widget_font_size +logger = logging.getLogger(__name__) + class SearchThread(QtCore.QThread): searchComplete = pyqtSignal() @@ -296,7 +299,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog): self.progdialog.exec() def search_canceled(self): - print("query cancelled") + logger.info("query cancelled") self.search_thread.searchComplete.disconnect(self.search_complete) self.search_thread.progressUpdate.disconnect(self.search_progress_update) self.progdialog.canceled.disconnect(self.search_canceled) @@ -336,7 +339,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog): ) ) except: - print("bad data error filtering filter publishers") + logger.exception("bad data error filtering filter publishers") # pre sort the data - so that we can put exact matches first afterwards # compare as str incase extra chars ie. '1976?' @@ -350,14 +353,14 @@ class VolumeSelectionWindow(QtWidgets.QDialog): reverse=True, ) except: - print("bad data error sorting results by start_year,count_of_issues") + logger.exception("bad data error sorting results by start_year,count_of_issues") else: try: self.cv_search_results = sorted( self.cv_search_results, key=lambda i: str(i["count_of_issues"]), reverse=True ) except: - print("bad data error sorting results by count_of_issues") + logger.exception("bad data error sorting results by count_of_issues") # move sanitized matches to the front if self.settings.exact_series_matches_first: @@ -371,7 +374,7 @@ class VolumeSelectionWindow(QtWidgets.QDialog): ) self.cv_search_results = exact_matches + non_matches except: - print("bad data error filtering exact/near matches") + logger.exception("bad data error filtering exact/near matches") self.update_buttons()