Compare commits

...

5 Commits

Author SHA1 Message Date
2bec6d7ad5 more stuff
Some checks failed
CI / lint (ubuntu-latest, 3.9) (push) Failing after 16s
CI / build-and-test (macos-13, 3.9) (push) Has been cancelled
CI / build-and-test (macos-14, 3.9) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.9) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.9) (push) Has been cancelled
2025-06-18 19:38:13 -07:00
1a574b661a stuff 2025-06-18 19:38:05 -07:00
3f6facf45b Merge branch 'Kijaru/add_gtin_identifier' into develop
Some checks failed
CI / lint (ubuntu-latest, 3.9) (push) Failing after 33s
CI / build-and-test (macos-13, 3.9) (push) Has been cancelled
CI / build-and-test (macos-14, 3.9) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.9) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.9) (push) Has been cancelled
2025-06-08 13:24:26 -07:00
94f325a088 Fix error when parsing metadata from the CLI 2025-05-24 11:49:56 -07:00
ebd7fae059 Fix setting the issue to "1" when not searching online 2025-05-24 11:49:38 -07:00
5 changed files with 23 additions and 106 deletions

View File

@ -9,102 +9,13 @@ import zipfile
from typing import cast
import chardet
from zipremove import ZipFile
from comicapi.archivers import Archiver
logger = logging.getLogger(__name__)
class ZipFile(zipfile.ZipFile):
def remove(self, zinfo_or_arcname): # type: ignore
"""Remove a member from the archive."""
if self.mode not in ("w", "x", "a"):
raise ValueError("remove() requires mode 'w', 'x', or 'a'")
if not self.fp:
raise ValueError("Attempt to write to ZIP archive that was already closed")
if self._writing: # type: ignore[attr-defined]
raise ValueError("Can't write to ZIP archive while an open writing handle exists")
# Make sure we have an existing info object
if isinstance(zinfo_or_arcname, zipfile.ZipInfo):
zinfo = zinfo_or_arcname
# make sure zinfo exists
if zinfo not in self.filelist:
raise KeyError("There is no item %r in the archive" % zinfo_or_arcname)
else:
# get the info object
zinfo = self.getinfo(zinfo_or_arcname)
return self._remove_members({zinfo})
def _remove_members(self, members, *, remove_physical=True, chunk_size=2**20): # type: ignore
"""Remove members in a zip file.
All members (as zinfo) should exist in the zip; otherwise the zip file
will erroneously end in an inconsistent state.
"""
fp = self.fp
assert fp
entry_offset = 0
member_seen = False
# get a sorted filelist by header offset, in case the dir order
# doesn't match the actual entry order
filelist = sorted(self.filelist, key=lambda x: x.header_offset)
for i in range(len(filelist)):
info = filelist[i]
is_member = info in members
if not (member_seen or is_member):
continue
# get the total size of the entry
try:
offset = filelist[i + 1].header_offset
except IndexError:
offset = self.start_dir
entry_size = offset - info.header_offset
if is_member:
member_seen = True
entry_offset += entry_size
# update caches
self.filelist.remove(info)
try:
del self.NameToInfo[info.filename]
except KeyError:
pass
continue
# update the header and move entry data to the new position
if remove_physical:
old_header_offset = info.header_offset
info.header_offset -= entry_offset
read_size = 0
while read_size < entry_size:
fp.seek(old_header_offset + read_size)
data = fp.read(min(entry_size - read_size, chunk_size))
fp.seek(info.header_offset + read_size)
fp.write(data)
fp.flush()
read_size += len(data)
# Avoid missing entry if entries have a duplicated name.
# Reverse the order as NameToInfo normally stores the last added one.
for info in reversed(self.filelist):
self.NameToInfo.setdefault(info.filename, info)
# update state
if remove_physical:
self.start_dir -= entry_offset
self._didModify = True
# seek to the start of the central dir
fp.seek(self.start_dir)
class ZipArchiver(Archiver):
"""ZIP implementation"""
@ -149,7 +60,7 @@ class ZipArchiver(Archiver):
try:
with ZipFile(self.path, mode="a", allowZip64=True, compression=zipfile.ZIP_DEFLATED) as zf:
if archive_file in files:
zf.remove(archive_file)
zf.repack([zf.remove(archive_file)])
return True
except (zipfile.BadZipfile, OSError) as e:
logger.error("Error writing zip archive [%s]: %s :: %s", e, self.path, archive_file)
@ -163,7 +74,7 @@ class ZipArchiver(Archiver):
# now just add the archive file as a new one
with ZipFile(self.path, mode="a", allowZip64=True, compression=zipfile.ZIP_DEFLATED) as zf:
if archive_file in files:
zf.remove(archive_file)
zf.repack([zf.remove(archive_file)])
zf.writestr(archive_file, data)
return True
except (zipfile.BadZipfile, OSError) as e:

View File

@ -590,9 +590,6 @@ class CLI:
self.output(f"Processing {utils.path_to_short_str(ca.path)}...")
md, tags_read = self.create_local_metadata(ca, self.config.Runtime_Options__tags_read)
if md.issue is None or md.issue == "":
if self.config.Auto_Tag__assume_issue_one:
md.issue = "1"
matches: list[IssueResult] = []
# now, search online
@ -629,11 +626,15 @@ class CLI:
return res, match_results
else:
qt_md = self.try_quick_tag(ca, md)
query_md = md.copy()
qt_md = self.try_quick_tag(ca, query_md)
if query_md.issue is None or query_md.issue == "":
if self.config.Auto_Tag__assume_issue_one:
query_md.issue = "1"
if qt_md is None or qt_md.is_empty:
if qt_md is not None:
self.output("Failed to find match via quick tag")
ct_md, matches, res, match_results = self.normal_tag(ca, tags_read, md, match_results) # type: ignore[assignment]
ct_md, matches, res, match_results = self.normal_tag(ca, tags_read, query_md, match_results) # type: ignore[assignment]
if res is not None:
return res, match_results
else:

View File

@ -194,7 +194,7 @@ def parse_metadata_from_string(mdstr: str) -> GenericMetadata:
else:
value = t(value)
except (ValueError, TypeError):
raise argparse.ArgumentTypeError(f"Invalid syntax for tag '{key}': {value}")
raise argparse.ArgumentTypeError(f"Invalid syntax for tag {key!r}: {value!r}")
return value
md = GenericMetadata()
@ -240,6 +240,8 @@ def parse_metadata_from_string(mdstr: str) -> GenericMetadata:
else:
raise argparse.ArgumentTypeError(f"'{key}' is not a valid tag name")
md.is_empty = empty
except argparse.ArgumentTypeError as e:
raise e
except Exception as e:
logger.exception("Unable to read metadata from the commandline '%s'", mdstr)
raise Exception("Unable to read metadata from the commandline") from e

View File

@ -50,6 +50,8 @@ install_requires =
text2digits
typing-extensions>=4.3.0
wordninja
zipremove
pyicu
python_requires = >=3.9
[options.packages.find]
@ -73,16 +75,16 @@ pyinstaller40 =
[options.extras_require]
7z =
py7zr
py7zr<1
all =
PyQt5
PyQtWebEngine
comicinfoxml==0.4.*
comicinfoxml==0.5.*
gcd-talker>0.1.0
metron-talker>0.1.5
pillow-avif-plugin>=1.4.1
pillow-jxl-plugin>=1.2.5
py7zr
py7zr<1
rarfile>=4.0
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
archived_tags =
@ -92,13 +94,12 @@ avif =
cbr =
rarfile>=4.0
cix =
comicinfoxml==0.4.*
comicinfoxml==0.5.*
gcd =
gcd-talker>0.1.0
gui =
PyQt5
icu =
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
jxl =
pillow-jxl-plugin>=1.2.5
metron =
@ -106,10 +107,10 @@ metron =
pyinstaller =
PyQt5
PyQtWebEngine
comicinfoxml==0.4.*
comicinfoxml==0.5.*
pillow-avif-plugin>=1.4.1
pillow-jxl-plugin>=1.2.5
py7zr
py7zr<1
rarfile>=4.0
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
qtw =

View File

@ -28,7 +28,9 @@ def test_os_sorted():
"!cover",
]
assert comicapi.utils.os_sorted(page_name_list) == [
sorted_list = comicapi.utils.os_sorted(page_name_list)
assert sorted_list == [
"!cover",
"!cover.jpg",
"!cover.tar.gz",