Compare commits

...

9 Commits

Author SHA1 Message Date
785c987ba6 Run tests in parallel
Some checks failed
CI / lint (ubuntu-latest, 3.9) (push) Failing after 16s
Contributions / A job to automate contrib in readme (push) Failing after 14s
CI / build-and-test (macos-13, 3.13) (push) Has been cancelled
CI / build-and-test (macos-13, 3.9) (push) Has been cancelled
CI / build-and-test (macos-14, 3.13) (push) Has been cancelled
CI / build-and-test (macos-14, 3.9) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.13) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.9) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.13) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.9) (push) Has been cancelled
2025-06-18 21:08:27 -07:00
8ecf70bca2 Require pyicu 2025-06-18 21:08:27 -07:00
eda794ac09 Bump comicinfoxml 2025-06-18 21:08:27 -07:00
c36e4703d0 Use zipremove 2025-06-18 21:08:27 -07:00
818c3768ad Fix isort 2025-06-18 21:08:27 -07:00
5100c9640e Fix 7z 2025-06-18 21:08:27 -07:00
0a0c8f32fe Update build for linux arm64 release 2025-06-18 21:08:27 -07:00
f4e2b5305c Fix enabling original hash widgets 2025-06-18 21:08:27 -07:00
11e2dea0b1 Test python 3.9 and 3.13 publish 3.13 binaries 2025-06-18 21:08:27 -07:00
7 changed files with 43 additions and 148 deletions

View File

@ -46,7 +46,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.9]
python-version: [3.9, 3.13]
os: [ubuntu-22.04, macos-13, macos-14, windows-latest]
steps:
@ -70,7 +70,7 @@ jobs:
- name: Install linux dependencies
run: |
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt5gui5 libfuse2 desktop-file-utils
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt6gui6 libfuse2 desktop-file-utils
if: runner.os == 'Linux'
- name: Build and install PyPi packages
@ -90,7 +90,9 @@ jobs:
dist/binary/*.tar.gz
dist/binary/*.dmg
dist/binary/*.AppImage
if: matrix.python == 3.12
- name: PyTest
run: |
python -m tox r
python -m tox p -e py${{ matrix.python-version }}-none,py${{ matrix.python-version }}-gui,py${{ matrix.python-version }}-7z,py${{ matrix.python-version }}-cbr,py${{ matrix.python-version }}-all
shell: bash

View File

@ -15,8 +15,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.9]
os: [ubuntu-22.04, macos-13, macos-14, windows-latest]
python-version: [3.13]
os: [ubuntu-22.04, ubuntu-22.04-arm, macos-13, macos-14, windows-latest]
steps:
- uses: actions/checkout@v4
@ -39,19 +39,22 @@ jobs:
- name: Install linux dependencies
run: |
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt5gui5 libfuse2 desktop-file-utils
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt6gui6 libfuse2 desktop-file-utils
if: runner.os == 'Linux'
- name: Build, Install and Test PyPi packages
run: |
export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:/opt/homebrew/opt/icu4c/lib/pkgconfig${PKG_CONFIG_PATH+:$PKG_CONFIG_PATH}";
export PATH="/usr/local/opt/icu4c/bin:/usr/local/opt/icu4c/sbin${PATH+:$PATH}"
python -m tox r
python -m tox r -m release
python -m tox p
- name: Release PyPi package
run: |
python -m tox r -e pypi-upload
shell: bash
if: matrix.os == 'ubuntu-22.04'
- name: Get release name
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
git fetch --depth=1 origin +refs/tags/*:refs/tags/* # github is dumb
@ -70,4 +73,4 @@ jobs:
dist/binary/*.tar.gz
dist/binary/*.dmg
dist/binary/*.AppImage
dist/*${{ fromJSON('["never", ""]')[runner.os == 'Linux'] }}.whl
dist/*${{ fromJSON('["never", ""]')[matrix.os == 'ubuntu-22.04'] }}.whl

View File

@ -55,20 +55,15 @@ def Tar(tar_file: pathlib.Path, path: pathlib.Path) -> None:
if __name__ == "__main__":
app = "ComicTagger"
exe = app.casefold()
final_name = f"{app}-{__version__}-{platform.system()}-{platform.machine()}"
if platform.system() == "Windows":
os_version = f"win-{platform.machine()}"
app_name = f"{exe}.exe"
final_name = f"{app}-{__version__}-{os_version}.exe"
exe = f"{exe}.exe"
elif platform.system() == "Darwin":
exe = f"{app}.app"
ver = platform.mac_ver()
os_version = f"osx-{ver[0]}-{ver[2]}"
app_name = f"{app}.app"
final_name = f"{app}-{__version__}-{os_version}"
else:
app_name = exe
final_name = f"ComicTagger-{__version__}-{platform.system()}"
final_name = f"{app}-{__version__}-macOS-{ver[0]}-{ver[2]}"
path = pathlib.Path(f"dist/{app_name}")
path = pathlib.Path(f"dist/{exe}")
binary_path = pathlib.Path("dist/binary")
binary_path.mkdir(parents=True, exist_ok=True)
archive_destination = binary_path / final_name

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

@ -449,7 +449,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
def toggle_enable_embedding_hashes(self) -> None:
self.config[0].Runtime_Options__enable_embedding_hashes = self.actionEnableEmbeddingHashes.isChecked()
enable_widget(self.md_attributes["original_hash"], self.config[0].Runtime_Options__enable_embedding_hashes)
enabled_widgets = set()
for tag_id in self.selected_write_tags:
if not tags[tag_id].enabled:
continue
enabled_widgets.update(tags[tag_id].supported_attributes)
enable_widget(
self.md_attributes["original_hash"],
self.config[0].Runtime_Options__enable_embedding_hashes and "original_hash" in enabled_widgets,
)
if not self.leOriginalHash.text().strip():
self.cbHashName.setCurrentText(self.config[0].internal__embedded_hash_type)
if self.config[0].Runtime_Options__enable_embedding_hashes:

View File

@ -5,7 +5,7 @@ extend-exclude = "comictaggerlib/graphics/resources.py"
[tool.isort]
line_length = 120
extend_skip = ["scripts", "comictaggerlib/graphics/resources.py"]
extend_skip_glob = ["scripts", "comictaggerlib/graphics/resources.py"]
profile = "black"
[build-system]

View File

@ -50,6 +50,8 @@ install_requires =
text2digits
typing-extensions>=4.3.0
wordninja
zipremove
pyicu;sys_platform != 'windows'
python_requires = >=3.9
[options.packages.find]
@ -73,18 +75,17 @@ pyinstaller40 =
[options.extras_require]
7z =
py7zr
py7zr<1
all =
PyQt6
PyQt6-WebEngine
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 =
ct-archived-tags
avif =
@ -92,7 +93,7 @@ avif =
cbr =
rarfile>=4.0
cix =
comicinfoxml==0.4.*
comicinfoxml==0.5.*
gcd =
gcd-talker>0.1.0
gui =
@ -106,12 +107,11 @@ metron =
pyinstaller =
PyQt6
PyQt6-WebEngine
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 =
PyQt6
PyQt6-WebEngine
@ -126,7 +126,7 @@ comictaggerlib =
[tox:tox]
env_list =
format
py3.9-{none,gui,7z,cbr,icu,all}
py3.9-{none,gui,7z,cbr,all}
minversion = 4.4.12
basepython = {env:tox_python:python3.9}
@ -139,28 +139,10 @@ extras =
7z: 7Z
cbr: CBR
gui: GUI
icu: ICU
all: all
commands =
python -m pytest {tty:--color=yes} {posargs}
icu,all: python -c 'import importlib,platform; importlib.import_module("icu") if platform.system() != "Windows" else ...' # Sanity check for icu
[m1env]
description = run the tests with pytest
package = wheel
deps =
pytest>=7
icu,all: pyicu-binary
extras =
7z: 7Z
cbr: CBR
gui: GUI
all: 7Z,CBR,GUI
commands =
python -m pytest {tty:--color=yes} {posargs}
[testenv:py3.9-{icu,all}]
base = {env:tox_env:testenv}
python -c 'import importlib,platform; importlib.import_module("icu") if platform.system() != "Windows" else ...' # Sanity check for icu
[testenv:format]
labels =
@ -212,7 +194,6 @@ commands =
[testenv:wheel]
description = Generate wheel and tar.gz
labels =
release
build
depends = clean
skip_install = true
@ -224,8 +205,6 @@ commands =
[testenv:pypi-upload]
description = Upload wheel to PyPi
platform = linux
labels =
release
skip_install = true
depends = wheel
deps =
@ -246,7 +225,6 @@ commands =
description = Generate pyinstaller executable
labels =
build
release
base = {env:tox_env:testenv}
depends =
clean
@ -264,7 +242,6 @@ skip_install = true
platform = linux
base = {env:tox_env:testenv}
labels =
release
build
depends =
clean
@ -289,7 +266,6 @@ commands =
[testenv:zip_artifacts]
description = Zip release artifacts
labels =
release
build
depends =
wheel