Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
e5f6a7d1d6 | |||
e7f937ecd2 | |||
3f9e5457f6 | |||
cc2ef8593c | |||
c5a5fc8bdb | |||
1cbed64299 | |||
c608ff80a1 | |||
76fb565d4e | |||
880b1be401 | |||
c469fdb25e | |||
685ce014b6 | |||
62bf1d3808 | |||
d55d75cd79 | |||
70293a0819 | |||
8592fdee74 | |||
618e15600f | |||
73dd33dc64 | |||
3774ab0568 | |||
f8807675d6 | |||
79137a12f8 | |||
d33d274725 |
1
.github/workflows/package.yaml
vendored
1
.github/workflows/package.yaml
vendored
@ -78,6 +78,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch --depth=1 origin +refs/tags/*:refs/tags/* # github is dumb
|
||||
echo "release_name=$(git tag -l --format "%(refname:strip=2): %(contents:lines=1)" ${{ github.ref_name }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Release
|
||||
|
@ -1,7 +1,7 @@
|
||||
exclude: ^scripts
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@ -19,7 +19,7 @@ repos:
|
||||
- id: isort
|
||||
args: [--af,--add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.1.0
|
||||
rev: v3.2.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
@ -33,12 +33,12 @@ repos:
|
||||
- id: autoflake
|
||||
args: [-i]
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-encodings, flake8-warnings, flake8-builtins, flake8-length, flake8-print]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.982
|
||||
rev: v0.991
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-setuptools, types-requests]
|
||||
|
2
Makefile
2
Makefile
@ -34,7 +34,7 @@ $(PYTHON_VENV):
|
||||
$(PY3) -m venv $(VENV)
|
||||
|
||||
clean:
|
||||
find . -maxdepth 4 -type d -name "__pycache__"
|
||||
find . -maxdepth 4 -type d -name "__pycache__" -print -depth -exec rm -rf {} \;
|
||||
rm -rf $(PACKAGE_PATH) $(INSTALL_STAMP) build dist MANIFEST comictaggerlib/ctversion.py
|
||||
$(MAKE) -C mac clean
|
||||
|
||||
|
@ -28,7 +28,6 @@ import zipfile
|
||||
from typing import cast
|
||||
|
||||
import natsort
|
||||
import py7zr
|
||||
import wordninja
|
||||
|
||||
from comicapi import filenamelexer, filenameparser, utils
|
||||
@ -37,6 +36,12 @@ from comicapi.comicbookinfo import ComicBookInfo
|
||||
from comicapi.comicinfoxml import ComicInfoXml
|
||||
from comicapi.genericmetadata import GenericMetadata, PageType
|
||||
|
||||
try:
|
||||
import py7zr
|
||||
|
||||
z7_support = True
|
||||
except ImportError:
|
||||
z7_support = False
|
||||
try:
|
||||
from unrar.cffi import rarfile
|
||||
|
||||
@ -756,15 +761,13 @@ class ComicArchive:
|
||||
self.archiver.path = pathlib.Path(path)
|
||||
|
||||
def sevenzip_test(self) -> bool:
|
||||
return py7zr.is_7zfile(self.path)
|
||||
return z7_support and py7zr.is_7zfile(self.path)
|
||||
|
||||
def zip_test(self) -> bool:
|
||||
return zipfile.is_zipfile(self.path)
|
||||
|
||||
def rar_test(self) -> bool:
|
||||
if rar_support:
|
||||
return rarfile.is_rarfile(str(self.path))
|
||||
return False
|
||||
return rar_support and rarfile.is_rarfile(str(self.path))
|
||||
|
||||
def folder_test(self) -> bool:
|
||||
return self.path.is_dir()
|
||||
|
@ -137,17 +137,11 @@ class ComicInfoXml:
|
||||
|
||||
# second, convert each list to string, and add to XML struct
|
||||
assign("Writer", ", ".join(credit_writer_list))
|
||||
|
||||
assign("Penciller", ", ".join(credit_penciller_list))
|
||||
|
||||
assign("Inker", ", ".join(credit_inker_list))
|
||||
|
||||
assign("Colorist", ", ".join(credit_colorist_list))
|
||||
|
||||
assign("Letterer", ", ".join(credit_letterer_list))
|
||||
|
||||
assign("CoverArtist", ", ".join(credit_cover_list))
|
||||
|
||||
assign("Editor", ", ".join(credit_editor_list))
|
||||
|
||||
assign("Publisher", md.publisher)
|
||||
|
@ -90,7 +90,8 @@ class Item:
|
||||
class Lexer:
|
||||
def __init__(self, string: str) -> None:
|
||||
self.input: str = string # The string being scanned
|
||||
self.state: Callable[[Lexer], Callable | None] | None = None # The next lexing function to enter
|
||||
# The next lexing function to enter
|
||||
self.state: Callable[[Lexer], Callable | None] | None = None # type: ignore[type-arg]
|
||||
self.pos: int = -1 # Current position in the input
|
||||
self.start: int = 0 # Start position of this item
|
||||
self.lastPos: int = 0 # Position of most recent item returned by nextItem
|
||||
@ -172,13 +173,13 @@ class Lexer:
|
||||
|
||||
# Errorf returns an error token and terminates the scan by passing
|
||||
# Back a nil pointer that will be the next state, terminating self.nextItem.
|
||||
def errorf(lex: Lexer, message: str) -> Callable[[Lexer], Callable | None] | None:
|
||||
def errorf(lex: Lexer, message: str) -> Callable[[Lexer], Callable | None] | None: # type: ignore[type-arg]
|
||||
lex.items.append(Item(ItemType.Error, lex.start, message))
|
||||
return None
|
||||
|
||||
|
||||
# Scans the elements inside action delimiters.
|
||||
def lex_filename(lex: Lexer) -> Callable[[Lexer], Callable | None] | None:
|
||||
def lex_filename(lex: Lexer) -> Callable[[Lexer], Callable | None] | None: # type: ignore[type-arg]
|
||||
r = lex.get()
|
||||
if r == eof:
|
||||
if lex.paren_depth != 0:
|
||||
@ -257,7 +258,7 @@ def lex_filename(lex: Lexer) -> Callable[[Lexer], Callable | None] | None:
|
||||
return lex_filename
|
||||
|
||||
|
||||
def lex_operator(lex: Lexer) -> Callable:
|
||||
def lex_operator(lex: Lexer) -> Callable: # type: ignore[type-arg]
|
||||
lex.accept_run("-|:;")
|
||||
lex.emit(ItemType.Operator)
|
||||
return lex_filename
|
||||
@ -265,7 +266,7 @@ def lex_operator(lex: Lexer) -> Callable:
|
||||
|
||||
# LexSpace scans a run of space characters.
|
||||
# One space has already been seen.
|
||||
def lex_space(lex: Lexer) -> Callable:
|
||||
def lex_space(lex: Lexer) -> Callable: # type: ignore[type-arg]
|
||||
while is_space(lex.peek()):
|
||||
lex.get()
|
||||
|
||||
@ -274,7 +275,7 @@ def lex_space(lex: Lexer) -> Callable:
|
||||
|
||||
|
||||
# Lex_text scans an alphanumeric.
|
||||
def lex_text(lex: Lexer) -> Callable:
|
||||
def lex_text(lex: Lexer) -> Callable: # type: ignore[type-arg]
|
||||
while True:
|
||||
r = lex.get()
|
||||
if is_alpha_numeric(r):
|
||||
@ -313,7 +314,7 @@ def cal(value: str) -> set[Any]:
|
||||
return set(month_abbr + month_name + day_abbr + day_name)
|
||||
|
||||
|
||||
def lex_number(lex: Lexer) -> Callable[[Lexer], Callable | None] | None:
|
||||
def lex_number(lex: Lexer) -> Callable[[Lexer], Callable | None] | None: # type: ignore[type-arg]
|
||||
if not lex.scan_number():
|
||||
return errorf(lex, "bad number syntax: " + lex.input[lex.start : lex.pos])
|
||||
# Complex number logic removed. Messes with math operations without space
|
||||
|
@ -343,7 +343,7 @@ class Parser:
|
||||
remove_fcbd: bool = False,
|
||||
remove_publisher: bool = False,
|
||||
) -> None:
|
||||
self.state: Callable[[Parser], Callable | None] | None = None
|
||||
self.state: Callable[[Parser], Callable | None] | None = None # type: ignore[type-arg]
|
||||
self.pos = -1
|
||||
|
||||
self.firstItem = True
|
||||
@ -412,7 +412,7 @@ class Parser:
|
||||
self.state = self.state(self)
|
||||
|
||||
|
||||
def parse(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
def parse(p: Parser) -> Callable[[Parser], Callable | None] | None: # type: ignore[type-arg]
|
||||
item: filenamelexer.Item = p.get()
|
||||
|
||||
# We're done, time to do final processing
|
||||
@ -670,7 +670,7 @@ def parse(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
|
||||
|
||||
# TODO: What about more esoteric numbers???
|
||||
def parse_issue_number(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
def parse_issue_number(p: Parser) -> Callable[[Parser], Callable | None] | None: # type: ignore[type-arg]
|
||||
item = p.input[p.pos]
|
||||
|
||||
if "issue" in p.filename_info:
|
||||
@ -702,7 +702,7 @@ def parse_issue_number(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
return parse
|
||||
|
||||
|
||||
def parse_series(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
def parse_series(p: Parser) -> Callable[[Parser], Callable | None] | None: # type: ignore[type-arg]
|
||||
item = p.input[p.pos]
|
||||
|
||||
series: list[list[filenamelexer.Item]] = [[]]
|
||||
@ -908,7 +908,7 @@ def resolve_issue(p: Parser) -> None:
|
||||
p.filename_info["issue"] = p.filename_info["volume"]
|
||||
|
||||
|
||||
def parse_finish(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
def parse_finish(p: Parser) -> Callable[[Parser], Callable | None] | None: # type: ignore[type-arg]
|
||||
resolve_year(p)
|
||||
resolve_issue(p)
|
||||
|
||||
@ -1014,7 +1014,7 @@ def get_remainder(p: Parser) -> str:
|
||||
return remainder.strip()
|
||||
|
||||
|
||||
def parse_info_specifier(p: Parser) -> Callable[[Parser], Callable | None] | None:
|
||||
def parse_info_specifier(p: Parser) -> Callable[[Parser], Callable | None] | None: # type: ignore[type-arg]
|
||||
item = p.input[p.pos]
|
||||
index = p.pos
|
||||
|
||||
|
@ -78,6 +78,7 @@ class GenericMetadata:
|
||||
|
||||
is_empty: bool = True
|
||||
tag_origin: str | None = None
|
||||
issue_id: int | None = None
|
||||
|
||||
series: str | None = None
|
||||
issue: str | None = None
|
||||
@ -128,7 +129,7 @@ class GenericMetadata:
|
||||
last_mark: str | None = None
|
||||
cover_image: str | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
def __post_init__(self) -> None:
|
||||
for key, value in self.__dict__.items():
|
||||
if value and key != "is_empty":
|
||||
self.is_empty = False
|
||||
|
@ -37,6 +37,14 @@ class UtilsVars:
|
||||
already_fixed_encoding = False
|
||||
|
||||
|
||||
def combine_notes(existing_notes: str | None, new_notes: str | None, split: str) -> str:
|
||||
split_notes, split_str, untouched_notes = (existing_notes or "").rpartition(split)
|
||||
if split_notes or split_str:
|
||||
return (split_notes + (new_notes or "")).strip()
|
||||
else:
|
||||
return (untouched_notes + "\n" + (new_notes or "")).strip()
|
||||
|
||||
|
||||
def parse_date_str(date_str: str) -> tuple[int | None, int | None, int | None]:
|
||||
day = None
|
||||
month = None
|
||||
|
@ -93,15 +93,6 @@ if platform.system() not in ["Windows"]:
|
||||
"CFBundleShortVersionString": ctversion.version,
|
||||
"CFBundleVersion": ctversion.version,
|
||||
"CFBundleDocumentTypes": [
|
||||
{
|
||||
"CFBundleTypeRole": "Viewer",
|
||||
"LSItemContentTypes": [
|
||||
"com.rarlab.rar-archive",
|
||||
],
|
||||
"CFBundleTypeName": "RAR Archive",
|
||||
"CFBundleTypeRole": "Editor",
|
||||
"LSHandlerRank": "Default",
|
||||
},
|
||||
{
|
||||
"CFBundleTypeRole": "Editor",
|
||||
"LSHandlerRank": "Default",
|
||||
@ -118,7 +109,6 @@ if platform.system() not in ["Windows"]:
|
||||
"NSPersistentStoreTypeKey": "Binary",
|
||||
"CFBundleTypeIconSystemGenerated": True,
|
||||
"CFBundleTypeName": "ZIP Comic Archive",
|
||||
# 'CFBundleTypeIconFile': 'cbz',
|
||||
"LSItemContentTypes": [
|
||||
"public.zip-comic-archive",
|
||||
"com.simplecomic.cbz-archive",
|
||||
@ -129,6 +119,7 @@ if platform.system() not in ["Windows"]:
|
||||
"com.milke.cbz-archive",
|
||||
"com.bitcartel.comicbooklover.cbz",
|
||||
"public.archive.cbz",
|
||||
"public.zip-archive",
|
||||
],
|
||||
"CFBundleTypeRole": "Editor",
|
||||
"LSHandlerRank": "Default",
|
||||
@ -141,8 +132,8 @@ if platform.system() not in ["Windows"]:
|
||||
"NSPersistentStoreTypeKey": "Binary",
|
||||
"CFBundleTypeIconSystemGenerated": True,
|
||||
"CFBundleTypeName": "7-Zip Comic Archive",
|
||||
# 'CFBundleTypeIconFile': 'cb7',
|
||||
"LSItemContentTypes": [
|
||||
"org.7-zip.7-zip-archive",
|
||||
"com.simplecomic.cb7-archive",
|
||||
"public.cb7-archive",
|
||||
"com.macitbetter.cb7-archive",
|
||||
@ -160,8 +151,8 @@ if platform.system() not in ["Windows"]:
|
||||
"NSPersistentStoreTypeKey": "Binary",
|
||||
"CFBundleTypeIconSystemGenerated": True,
|
||||
"CFBundleTypeName": "RAR Comic Archive",
|
||||
# 'CFBundleTypeIconFile': 'cbr',
|
||||
"LSItemContentTypes": [
|
||||
"com.rarlab.rar-archive",
|
||||
"com.rarlab.rar-comic-archive",
|
||||
"com.simplecomic.cbr-archive",
|
||||
"com.macitbetter.cbr-archive",
|
||||
@ -195,16 +186,11 @@ if platform.system() not in ["Windows"]:
|
||||
},
|
||||
},
|
||||
{
|
||||
# 'UTTypeIcons': {
|
||||
# 'UTTypeIconText': 'cbr',
|
||||
# 'UTTypeIconBackgroundName': comic-fill
|
||||
# }
|
||||
"UTTypeConformsTo": [
|
||||
"public.data",
|
||||
"public.archive",
|
||||
"com.rarlab.rar-archive",
|
||||
],
|
||||
# 'UTTypeIconFile': 'cbr',
|
||||
"UTTypeIdentifier": "com.rarlab.rar-comic-archive",
|
||||
"UTTypeDescription": "RAR Comic Archive",
|
||||
"UTTypeTagSpecification": {
|
||||
@ -218,16 +204,11 @@ if platform.system() not in ["Windows"]:
|
||||
},
|
||||
},
|
||||
{
|
||||
# 'UTTypeIcons': {
|
||||
# 'UTTypeIconText': 'cbz',
|
||||
# 'UTTypeIconBackgroundName': 'comic-fill',
|
||||
# }
|
||||
"UTTypeConformsTo": [
|
||||
"public.data",
|
||||
"public.archive",
|
||||
"public.zip-archive",
|
||||
],
|
||||
# 'UTTypeIconFile': cbz,
|
||||
"UTTypeIdentifier": "public.zip-comic-archive",
|
||||
"UTTypeDescription": "ZIP Comic Archive",
|
||||
"UTTypeTagSpecification": {
|
||||
@ -237,16 +218,11 @@ if platform.system() not in ["Windows"]:
|
||||
},
|
||||
},
|
||||
{
|
||||
# 'UTTypeIcons': {
|
||||
# 'UTTypeIconText': 'cb7',
|
||||
# 'UTTypeIconBackgroundName': comic-fill
|
||||
# }
|
||||
"UTTypeConformsTo": [
|
||||
"public.data",
|
||||
"public.archive",
|
||||
"org.7-zip.7-zip-archive",
|
||||
],
|
||||
# 'UTTypeIconFile': cb7
|
||||
"UTTypeIdentifier": "org.7-zip.7-zip-comic-archive",
|
||||
"UTTypeDescription": "7-Zip Comic Archive",
|
||||
"UTTypeTagSpecification": {
|
||||
|
@ -22,11 +22,13 @@ import logging
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pprint import pprint
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive, MetaDataStyle
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib import ctversion
|
||||
from comictaggerlib.cbltransformer import CBLTransformer
|
||||
from comictaggerlib.comicvinetalker import ComicVineTalker, ComicVineTalkerException
|
||||
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
|
||||
@ -112,7 +114,11 @@ def display_match_set_for_choice(
|
||||
if opts.overwrite:
|
||||
md = cv_md
|
||||
else:
|
||||
md.overlay(cv_md)
|
||||
notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from Comic Vine on"
|
||||
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {cv_md.issue_id}]"
|
||||
)
|
||||
md.overlay(cv_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
|
||||
|
||||
if opts.auto_imprint:
|
||||
md.fix_publisher()
|
||||
@ -461,7 +467,11 @@ def process_file_cli(
|
||||
if opts.overwrite:
|
||||
md = cv_md
|
||||
else:
|
||||
md.overlay(cv_md)
|
||||
notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from Comic Vine on"
|
||||
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {cv_md.issue_id}]"
|
||||
)
|
||||
md.overlay(cv_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
|
||||
|
||||
if opts.auto_imprint:
|
||||
md.fix_publisher()
|
||||
|
@ -450,6 +450,8 @@ class ComicCacher:
|
||||
set_slots = ""
|
||||
|
||||
for key in data:
|
||||
if data[key] is None:
|
||||
continue
|
||||
|
||||
if keys != "":
|
||||
keys += ", "
|
||||
|
@ -19,7 +19,6 @@ import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, cast
|
||||
from urllib.parse import urlencode, urljoin, urlsplit
|
||||
|
||||
@ -327,10 +326,11 @@ class ComicVineTalker:
|
||||
|
||||
def fetch_issues_by_volume(self, series_id: int) -> list[resulttypes.CVIssuesResults]:
|
||||
# before we search online, look in our cache, since we might already have this info
|
||||
volume_data = self.fetch_volume_data(series_id)
|
||||
cvc = ComicCacher()
|
||||
cached_volume_issues_result = cvc.get_volume_issues_info(series_id, self.source_name)
|
||||
|
||||
if cached_volume_issues_result:
|
||||
if len(cached_volume_issues_result) >= volume_data["count_of_issues"]:
|
||||
return cached_volume_issues_result
|
||||
|
||||
params = {
|
||||
@ -407,6 +407,10 @@ class ComicVineTalker:
|
||||
|
||||
self.repair_urls(filtered_issues_result)
|
||||
|
||||
cvc = ComicCacher()
|
||||
for c in filtered_issues_result:
|
||||
cvc.add_volume_issues_info(self.source_name, c["volume"]["id"], [c])
|
||||
|
||||
return filtered_issues_result
|
||||
|
||||
def fetch_issue_data(self, series_id: int, issue_number: str, settings: ComicTaggerSettings) -> GenericMetadata:
|
||||
@ -461,6 +465,8 @@ class ComicVineTalker:
|
||||
# Now, map the Comic Vine data to generic metadata
|
||||
metadata = GenericMetadata()
|
||||
metadata.is_empty = False
|
||||
metadata.tag_origin = "Comic Vine"
|
||||
metadata.issue_id = issue_results["id"]
|
||||
|
||||
metadata.series = utils.xlate(issue_results["volume"]["name"])
|
||||
metadata.issue = IssueString(issue_results["issue_number"]).as_string()
|
||||
@ -474,10 +480,6 @@ class ComicVineTalker:
|
||||
if settings.use_series_start_as_volume:
|
||||
metadata.volume = int(volume_results["start_year"])
|
||||
|
||||
metadata.notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from Comic Vine on"
|
||||
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {issue_results['id']}]"
|
||||
)
|
||||
metadata.web_link = issue_results["site_detail_url"]
|
||||
|
||||
person_credits = issue_results["person_credits"]
|
||||
@ -671,7 +673,7 @@ class ComicVineTalker:
|
||||
|
||||
def fetch_alternate_cover_urls(self, issue_id: int, issue_page_url: str) -> list[str]:
|
||||
url_list = self.fetch_cached_alternate_cover_urls(issue_id)
|
||||
if url_list is not None:
|
||||
if url_list:
|
||||
return url_list
|
||||
|
||||
# scrape the CV issue page URL to get the alternate cover URLs
|
||||
|
@ -221,7 +221,7 @@ class FileRenamer:
|
||||
self.template = template
|
||||
|
||||
def determine_name(self, ext: str) -> str:
|
||||
class Default(dict):
|
||||
class Default(dict[str, Any]):
|
||||
def __missing__(self, key: str) -> str:
|
||||
return "{" + key + "}"
|
||||
|
||||
|
@ -251,6 +251,10 @@ class IssueIdentifier:
|
||||
# local_cover_hash_list is a list of pre-calculated hashes.
|
||||
# use_remote_alternates - indicates to use alternate covers from CV
|
||||
|
||||
# If there is no URL return 0
|
||||
if not primary_img_url:
|
||||
return Score(score=0, url="", hash=0)
|
||||
|
||||
try:
|
||||
url_image_data = ImageFetcher().fetch(primary_thumb_url, blocking=True)
|
||||
except ImageFetcherException as e:
|
||||
|
@ -94,7 +94,7 @@ try:
|
||||
class Application(QtWidgets.QApplication):
|
||||
openFileRequest = QtCore.pyqtSignal(QtCore.QUrl, name="openfileRequest")
|
||||
|
||||
def event(self, event):
|
||||
def event(self, event: QtCore.QEvent) -> bool:
|
||||
if event.type() == QtCore.QEvent.FileOpen:
|
||||
logger.info(event.url().toLocalFile())
|
||||
self.openFileRequest.emit(event.url())
|
||||
@ -145,7 +145,17 @@ def ctmain() -> None:
|
||||
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
||||
datefmt="%Y-%m-%dT%H:%M:%S",
|
||||
)
|
||||
# Need to load setting before anything else
|
||||
|
||||
if settings.settings_warning < 4:
|
||||
print( # noqa: T201
|
||||
"""
|
||||
!!!Warning!!!
|
||||
The next release will save settings in a different format
|
||||
NO SETTINGS WILL BE TRANSFERED to the new version.
|
||||
See https://github.com/comictagger/comictagger/releases/1.5.5 for more information.
|
||||
""",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# manage the CV API key
|
||||
# None comparison is used so that the empty string can unset the value
|
||||
|
@ -70,6 +70,7 @@ class ComicTaggerSettings:
|
||||
# Show/ask dialog flags
|
||||
self.ask_about_cbi_in_rar = True
|
||||
self.show_disclaimer = True
|
||||
self.settings_warning = 0
|
||||
self.dont_notify_about_this_version = ""
|
||||
self.ask_about_usage_stats = True
|
||||
|
||||
@ -158,9 +159,9 @@ class ComicTaggerSettings:
|
||||
# make sure rar program is now in the path for the rar class
|
||||
utils.add_to_path(os.path.dirname(self.rar_exe_path))
|
||||
|
||||
def reset(self):
|
||||
def reset(self) -> None:
|
||||
os.unlink(self.settings_file)
|
||||
self.__init__(ComicTaggerSettings.folder)
|
||||
self.__init__(ComicTaggerSettings.folder) # type: ignore[misc]
|
||||
|
||||
def load(self) -> None:
|
||||
def readline_generator(f: TextIO) -> Iterator[str]:
|
||||
@ -223,6 +224,8 @@ class ComicTaggerSettings:
|
||||
self.ask_about_cbi_in_rar = self.config.getboolean("dialogflags", "ask_about_cbi_in_rar")
|
||||
if self.config.has_option("dialogflags", "show_disclaimer"):
|
||||
self.show_disclaimer = self.config.getboolean("dialogflags", "show_disclaimer")
|
||||
if self.config.has_option("dialogflags", "settings_warning"):
|
||||
self.settings_warning = self.config.getint("dialogflags", "settings_warning")
|
||||
if self.config.has_option("dialogflags", "dont_notify_about_this_version"):
|
||||
self.dont_notify_about_this_version = self.config.get("dialogflags", "dont_notify_about_this_version")
|
||||
if self.config.has_option("dialogflags", "ask_about_usage_stats"):
|
||||
@ -349,6 +352,7 @@ class ComicTaggerSettings:
|
||||
|
||||
self.config.set("dialogflags", "ask_about_cbi_in_rar", self.ask_about_cbi_in_rar)
|
||||
self.config.set("dialogflags", "show_disclaimer", self.show_disclaimer)
|
||||
self.config.set("dialogflags", "settings_warning", self.settings_warning)
|
||||
self.config.set("dialogflags", "dont_notify_about_this_version", self.dont_notify_about_this_version)
|
||||
self.config.set("dialogflags", "ask_about_usage_stats", self.ask_about_usage_stats)
|
||||
|
||||
|
@ -27,6 +27,7 @@ import re
|
||||
import sys
|
||||
import webbrowser
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@ -260,8 +261,25 @@ Have fun!
|
||||
)
|
||||
self.settings.show_disclaimer = not checked
|
||||
|
||||
if self.settings.settings_warning < 4:
|
||||
checked = OptionalMessageDialog.msg(
|
||||
self,
|
||||
"Warning!",
|
||||
f"""<span style="font-size:15px">
|
||||
{" "*100}
|
||||
The next release will save settings in a different format
|
||||
<span style="font-weight: bold;font-size:19px">no settings will be transfered</span> to the new version.<br/>
|
||||
See <a href="https://github.com/comictagger/comictagger/releases/1.5.5">https://github.com/comictagger/comictagger/releases/1.5.5</a>
|
||||
for more information
|
||||
<br/><br/>
|
||||
You have {4-self.settings.settings_warning} warnings left.
|
||||
</span>""",
|
||||
)
|
||||
if checked:
|
||||
self.settings.settings_warning += 1
|
||||
|
||||
if self.settings.check_for_new_version:
|
||||
pass
|
||||
self.check_latest_version_online()
|
||||
|
||||
def open_file_event(self, url: QtCore.QUrl) -> None:
|
||||
logger.info(url.toLocalFile())
|
||||
@ -1090,7 +1108,15 @@ Have fun!
|
||||
if self.settings.clear_form_before_populating_from_cv:
|
||||
self.clear_form()
|
||||
|
||||
self.metadata.overlay(new_metadata)
|
||||
notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from Comic Vine on"
|
||||
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {new_metadata.issue_id}]"
|
||||
)
|
||||
self.metadata.overlay(
|
||||
new_metadata.replace(
|
||||
notes=utils.combine_notes(self.metadata.notes, notes, "Tagged with ComicTagger")
|
||||
)
|
||||
)
|
||||
# Now push the new combined data into the edit controls
|
||||
self.metadata_to_form()
|
||||
else:
|
||||
@ -1794,7 +1820,11 @@ Have fun!
|
||||
if dlg.cbxRemoveMetadata.isChecked():
|
||||
md = cv_md
|
||||
else:
|
||||
md.overlay(cv_md)
|
||||
notes = (
|
||||
f"Tagged with ComicTagger {ctversion.version} using info from Comic Vine on"
|
||||
f" {datetime.now():%Y-%m-%d %H:%M:%S}. [Issue ID {cv_md.issue_id}]"
|
||||
)
|
||||
md.overlay(cv_md.replace(notes=utils.combine_notes(md.notes, notes, "Tagged with ComicTagger")))
|
||||
|
||||
if self.settings.auto_imprint:
|
||||
md.fix_publisher()
|
||||
@ -2108,19 +2138,19 @@ Have fun!
|
||||
version_checker.get_latest_version(self.settings.install_id, self.settings.send_usage_stats)
|
||||
)
|
||||
|
||||
def version_check_complete(self, new_version: str) -> None:
|
||||
if new_version not in (self.version, self.settings.dont_notify_about_this_version):
|
||||
def version_check_complete(self, new_version: tuple[str, str]) -> None:
|
||||
if new_version[0] not in (self.version, self.settings.dont_notify_about_this_version):
|
||||
website = "https://github.com/comictagger/comictagger"
|
||||
checked = OptionalMessageDialog.msg(
|
||||
self,
|
||||
"New version available!",
|
||||
f"New version ({new_version}) available!<br>(You are currently running {self.version})<br><br>"
|
||||
f"Visit <a href='{website}'>{website}</a> for more info.<br><br>",
|
||||
f"New version ({new_version[1]}) available!<br>(You are currently running {self.version})<br><br>"
|
||||
f"Visit <a href='{website}/releases/latest'>{website}/releases/latest</a> for more info.<br><br>",
|
||||
False,
|
||||
"Don't tell me about this version again",
|
||||
)
|
||||
if checked:
|
||||
self.settings.dont_notify_about_this_version = new_version
|
||||
self.settings.dont_notify_about_this_version = new_version[0]
|
||||
|
||||
def on_incoming_socket_connection(self) -> None:
|
||||
# Accept connection from other instance.
|
||||
|
@ -16,8 +16,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
@ -29,31 +27,23 @@ logger = logging.getLogger(__name__)
|
||||
class VersionChecker:
|
||||
def get_request_url(self, uuid: str, use_stats: bool) -> tuple[str, dict[str, str]]:
|
||||
|
||||
base_url = "http://comictagger1.appspot.com/latest"
|
||||
params = {}
|
||||
if use_stats:
|
||||
params = {"uuid": uuid, "version": ctversion.version}
|
||||
if platform.system() == "Windows":
|
||||
params["platform"] = "win"
|
||||
elif platform.system() == "Linux":
|
||||
params["platform"] = "lin"
|
||||
elif platform.system() == "Darwin":
|
||||
params["platform"] = "mac"
|
||||
else:
|
||||
params["platform"] = "other"
|
||||
|
||||
if not getattr(sys, "frozen", None):
|
||||
params["src"] = "T"
|
||||
base_url = "https://api.github.com/repos/comictagger/comictagger/releases/latest"
|
||||
params: dict[str, str] = {}
|
||||
|
||||
return base_url, params
|
||||
|
||||
def get_latest_version(self, uuid: str, use_stats: bool = True) -> str:
|
||||
def get_latest_version(self, uuid: str, use_stats: bool = True) -> tuple[str, str]:
|
||||
try:
|
||||
url, params = self.get_request_url(uuid, use_stats)
|
||||
new_version = requests.get(url, params=params).text
|
||||
release = requests.get(
|
||||
url,
|
||||
params=params,
|
||||
headers={"user-agent": "comictagger/" + ctversion.version},
|
||||
).json()
|
||||
except Exception:
|
||||
return ""
|
||||
return ("", "")
|
||||
|
||||
new_version = release["tag_name"]
|
||||
if new_version is None or new_version == "":
|
||||
return ""
|
||||
return new_version.strip()
|
||||
return ("", "")
|
||||
return (new_version.strip(), release["name"])
|
||||
|
10
localefix.py
10
localefix.py
@ -7,7 +7,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def _lang_code_mac():
|
||||
def _lang_code_mac() -> str:
|
||||
"""
|
||||
stolen from https://github.com/mu-editor/mu
|
||||
Returns the user's language preference as defined in the Language & Region
|
||||
@ -38,13 +38,13 @@ def _lang_code_mac():
|
||||
return lang_code
|
||||
|
||||
|
||||
def configure_locale():
|
||||
def configure_locale() -> None:
|
||||
if sys.platform == "darwin" and "LANG" not in os.environ:
|
||||
code = _lang_code_mac()
|
||||
if code != "":
|
||||
os.environ["LANG"] = f"{code}.utf-8"
|
||||
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
sys.stdout.reconfigure(encoding=sys.getdefaultencoding())
|
||||
sys.stderr.reconfigure(encoding=sys.getdefaultencoding())
|
||||
sys.stdin.reconfigure(encoding=sys.getdefaultencoding())
|
||||
sys.stdout.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined]
|
||||
sys.stderr.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined]
|
||||
sys.stdin.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined]
|
||||
|
1
requirements-7Z.txt
Normal file
1
requirements-7Z.txt
Normal file
@ -0,0 +1 @@
|
||||
py7zr
|
@ -3,7 +3,6 @@ importlib_metadata>=3.3.0
|
||||
natsort>=8.1.0
|
||||
pathvalidate
|
||||
pillow>=9.1.0
|
||||
py7zr
|
||||
pycountry
|
||||
pyicu; sys_platform == 'linux' or sys_platform == 'darwin'
|
||||
rapidfuzz>=2.12.0
|
||||
|
@ -5,7 +5,7 @@ flake8-black
|
||||
flake8-encodings
|
||||
flake8-isort
|
||||
isort>=5.10
|
||||
pyinstaller>=4.10, != 5.6
|
||||
pyinstaller>=5.6.2
|
||||
pytest==7.*
|
||||
setuptools>=42
|
||||
setuptools_scm[toml]>=3.4
|
||||
|
@ -160,7 +160,8 @@ date = comictaggerlib.comicvinetalker.ComicVineTalker().parse_date_str(cv_issue_
|
||||
|
||||
cv_md = comicapi.genericmetadata.GenericMetadata(
|
||||
is_empty=False,
|
||||
tag_origin=None,
|
||||
tag_origin="Comic Vine",
|
||||
issue_id=cv_issue_result["results"]["id"],
|
||||
series=cv_issue_result["results"]["volume"]["name"],
|
||||
issue=cv_issue_result["results"]["issue_number"],
|
||||
title=cv_issue_result["results"]["name"],
|
||||
@ -182,7 +183,7 @@ cv_md = comicapi.genericmetadata.GenericMetadata(
|
||||
alternate_number=None,
|
||||
alternate_count=None,
|
||||
imprint=None,
|
||||
notes="Tagged with ComicTagger 1.4.4a9.dev20 using info from Comic Vine on 2022-07-11 17:42:41. [Issue ID 140529]",
|
||||
notes=None,
|
||||
web_link=cv_issue_result["results"]["site_detail_url"],
|
||||
format=None,
|
||||
manga=None,
|
||||
|
@ -119,8 +119,11 @@ def test_invalid_zip(tmp_comic):
|
||||
|
||||
archivers = [
|
||||
comicapi.comicarchive.ZipArchiver,
|
||||
comicapi.comicarchive.SevenZipArchiver,
|
||||
comicapi.comicarchive.FolderArchiver,
|
||||
pytest.param(
|
||||
comicapi.comicarchive.SevenZipArchiver,
|
||||
marks=pytest.mark.xfail(not (comicapi.comicarchive.z7_support), reason="7z support"),
|
||||
),
|
||||
pytest.param(
|
||||
comicapi.comicarchive.RarArchiver,
|
||||
marks=pytest.mark.xfail(not (comicapi.comicarchive.rar_support and shutil.which("rar")), reason="rar support"),
|
||||
|
@ -39,7 +39,7 @@ def test_fetch_issues_by_volume(comicvine_api, comic_cache):
|
||||
assert results == cache_issues
|
||||
|
||||
|
||||
def test_fetch_issue_data_by_issue_id(comicvine_api, settings, mock_now, mock_version):
|
||||
def test_fetch_issue_data_by_issue_id(comicvine_api, settings, mock_version):
|
||||
ct = comictaggerlib.comicvinetalker.ComicVineTalker()
|
||||
result = ct.fetch_issue_data_by_issue_id(140529, settings)
|
||||
assert result == testing.comicvine.cv_md
|
||||
@ -65,13 +65,13 @@ cv_issue = [
|
||||
|
||||
|
||||
@pytest.mark.parametrize("volume_id, issue_number, expected", cv_issue)
|
||||
def test_fetch_issue_data(comicvine_api, settings, mock_now, mock_version, volume_id, issue_number, expected):
|
||||
def test_fetch_issue_data(comicvine_api, settings, mock_version, volume_id, issue_number, expected):
|
||||
ct = comictaggerlib.comicvinetalker.ComicVineTalker()
|
||||
results = ct.fetch_issue_data(volume_id, issue_number, settings)
|
||||
assert results == expected
|
||||
|
||||
|
||||
def test_fetch_issue_select_details(comicvine_api, mock_now, mock_version):
|
||||
def test_fetch_issue_select_details(comicvine_api, mock_version):
|
||||
ct = comictaggerlib.comicvinetalker.ComicVineTalker()
|
||||
result = ct.fetch_issue_select_details(140529)
|
||||
expected = {
|
||||
|
@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import io
|
||||
import shutil
|
||||
import unittest.mock
|
||||
@ -115,18 +114,6 @@ def comicvine_api(monkeypatch, cbz, comic_cache) -> unittest.mock.Mock:
|
||||
return m_get
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_now(monkeypatch):
|
||||
class mydatetime:
|
||||
time = datetime.datetime(2022, 7, 11, 17, 42, 41)
|
||||
|
||||
@classmethod
|
||||
def now(cls):
|
||||
return cls.time
|
||||
|
||||
monkeypatch.setattr(comictaggerlib.comicvinetalker, "datetime", mydatetime)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_version(monkeypatch):
|
||||
version = "1.4.4a9.dev20"
|
||||
|
@ -77,6 +77,24 @@ def test_get_language_iso(value, result):
|
||||
assert result == comicapi.utils.get_language_iso(value)
|
||||
|
||||
|
||||
combine_values = [
|
||||
("hello", "english", "en", "hello\nenglish"),
|
||||
("hello en", "english", "en", "hello english"),
|
||||
("hello en goodbye", "english", "en", "hello english"),
|
||||
("hello en en goodbye", "english", "en", "hello en english"),
|
||||
("", "english", "en", "english"),
|
||||
(None, "english", "en", "english"),
|
||||
("hello", "", "en", "hello"),
|
||||
("hello", None, "en", "hello"),
|
||||
("hello", "hello", "hel", "hello"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("existing_notes, new_notes, split, result", combine_values)
|
||||
def test_combine_notes(existing_notes, new_notes, split, result):
|
||||
assert result == comicapi.utils.combine_notes(existing_notes, new_notes, split)
|
||||
|
||||
|
||||
def test_unique_file(tmp_path):
|
||||
file = tmp_path / "test.cbz"
|
||||
assert file == comicapi.utils.unique_file(file)
|
||||
|
Reference in New Issue
Block a user