Skip to content

Commit 72292fd

Browse files
authored
Merge-build: 2.9.0-beta
2 parents 50a878f + 70243b7 commit 72292fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+6501
-755
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""Generate hitomi-manifest.json for client-side download."""
2+
import sys
3+
import json
4+
import hashlib
5+
import sqlite3
6+
import argparse
7+
from pathlib import Path
8+
from datetime import datetime, timezone
9+
from contextlib import closing
10+
11+
12+
SOURCES = [
13+
{
14+
"id": "github",
15+
"priority": 10,
16+
"url": "https://github.com/jasoneri/ComicGUISpider/releases/download/preset/hitomi.db"
17+
},
18+
{
19+
"id": "gitee",
20+
"priority": 20,
21+
"url": "https://gitee.com/json_eri/ComicGUISpider/releases/download/preset/hitomi.db"
22+
},
23+
]
24+
25+
26+
def generate(db_path, output_path):
27+
db_file = Path(db_path)
28+
if not db_file.exists():
29+
print(f"[ERROR] {db_path} not found")
30+
return False
31+
32+
# sha256
33+
h = hashlib.sha256()
34+
with open(db_file, 'rb') as f:
35+
for chunk in iter(lambda: f.read(8192), b''):
36+
h.update(chunk)
37+
sha256 = h.hexdigest()
38+
size = db_file.stat().st_size
39+
40+
# stats
41+
table_count = 0
42+
row_count = 0
43+
with closing(sqlite3.connect(db_path)) as conn:
44+
cursor = conn.cursor()
45+
tables = cursor.execute(
46+
"SELECT name FROM sqlite_master WHERE type='table' AND name != 'language' AND name NOT LIKE 'sqlite_%'"
47+
).fetchall()
48+
table_count = len(tables)
49+
for (t,) in tables:
50+
count = cursor.execute(f"SELECT COUNT(*) FROM `{t}`").fetchone()[0]
51+
row_count += count
52+
53+
now = datetime.now(timezone.utc)
54+
manifest = {
55+
"schema_version": 1,
56+
"version": now.strftime("%Y.%m.%d.%H%M"),
57+
"generated_at": now.isoformat(),
58+
"file": {
59+
"name": "hitomi.db",
60+
"size": size,
61+
"sha256": sha256,
62+
"table_count": table_count,
63+
"row_count": row_count,
64+
},
65+
"sources": SOURCES,
66+
}
67+
68+
out = Path(output_path)
69+
out.parent.mkdir(parents=True, exist_ok=True)
70+
with open(out, 'w', encoding='utf-8') as f:
71+
json.dump(manifest, f, indent=2, ensure_ascii=False)
72+
73+
print(f"[OK] manifest written to {out}")
74+
print(f" version: {manifest['version']}")
75+
print(f" sha256: {sha256[:16]}...")
76+
print(f" size: {size} bytes")
77+
print(f" tables: {table_count}, rows: {row_count}")
78+
return True
79+
80+
81+
if __name__ == "__main__":
82+
parser = argparse.ArgumentParser(description="Generate hitomi-manifest.json")
83+
parser.add_argument("db_path", help="Path to hitomi.db")
84+
parser.add_argument("--output", "-o", default="hitomi-manifest.json", help="Output manifest path")
85+
args = parser.parse_args()
86+
sys.exit(0 if generate(args.db_path, args.output) else 1)

.github/scripts/hitomi_db_gate.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Quality gate for hitomi.db - validates integrity before release."""
2+
import sys
3+
import sqlite3
4+
from contextlib import closing
5+
6+
EXPECTED_TABLES = 108 # 4 categories x 27 letters
7+
MIN_TOTAL_ROWS = 5000
8+
9+
CATEGORIES = ['tags', 'artists', 'series', 'characters']
10+
LETTERS = [*[chr(i) for i in range(97, 123)], '123']
11+
12+
13+
def check(db_path):
14+
errors = []
15+
16+
with closing(sqlite3.connect(db_path)) as conn:
17+
cursor = conn.cursor()
18+
19+
# integrity check
20+
result = cursor.execute("PRAGMA integrity_check").fetchone()
21+
if not result or result[0] != "ok":
22+
errors.append(f"integrity_check failed: {result}")
23+
print("\n".join(errors))
24+
return False
25+
26+
# table existence + non-empty
27+
total_rows = 0
28+
missing = []
29+
empty = []
30+
31+
for cat in CATEGORIES:
32+
for letter in LETTERS:
33+
table_name = f"all{cat}-{letter}"
34+
try:
35+
count = cursor.execute(f"SELECT COUNT(*) FROM `{table_name}`").fetchone()[0]
36+
total_rows += count
37+
if count == 0:
38+
empty.append(table_name)
39+
except sqlite3.OperationalError:
40+
missing.append(table_name)
41+
42+
if missing:
43+
errors.append(f"missing tables ({len(missing)}): {missing[:5]}...")
44+
if empty:
45+
# empty tables are warnings, not errors (some letters may legitimately have no data)
46+
print(f"[WARN] empty tables ({len(empty)}): {empty[:10]}...")
47+
48+
found = EXPECTED_TABLES - len(missing)
49+
if found < EXPECTED_TABLES:
50+
errors.append(f"table count: {found}/{EXPECTED_TABLES}")
51+
52+
if total_rows < MIN_TOTAL_ROWS:
53+
errors.append(f"total rows {total_rows} < minimum {MIN_TOTAL_ROWS}")
54+
55+
# language table
56+
try:
57+
cursor.execute("SELECT COUNT(*) FROM `language`")
58+
except sqlite3.OperationalError:
59+
errors.append("language table missing")
60+
61+
if errors:
62+
print("[FAIL] Quality gate errors:")
63+
for e in errors:
64+
print(f" - {e}")
65+
return False
66+
67+
print(f"[PASS] {found} tables, {total_rows} total rows")
68+
return True
69+
70+
71+
if __name__ == "__main__":
72+
if len(sys.argv) < 2:
73+
print("Usage: hitomi_db_gate.py <db_path>")
74+
sys.exit(1)
75+
sys.exit(0 if check(sys.argv[1]) else 1)

.github/scripts/smoke_wheel.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
import subprocess
3+
import sys
4+
5+
6+
def run_test(name, cmd, env):
7+
print(f"::group::{name}")
8+
result = subprocess.run(cmd, env=env, text=True, capture_output=True)
9+
if result.stdout:
10+
print(result.stdout)
11+
if result.stderr:
12+
print(result.stderr, file=sys.stderr)
13+
print(f"exit_code={result.returncode}")
14+
print("::endgroup::")
15+
return result.returncode
16+
17+
18+
def main():
19+
env = os.environ.copy()
20+
tests = [
21+
("entrypoint import", [sys.executable, "-c", "import CGS"]),
22+
("check mode startup", [sys.executable, "-c", "import CGS; CGS.start()"]),
23+
("cli help", ["cgs-cli", "--help"]),
24+
]
25+
26+
failures = []
27+
for name, cmd in tests:
28+
if run_test(name, cmd, env) != 0:
29+
failures.append(name)
30+
31+
if failures:
32+
raise SystemExit(f"Smoke tests failed: {', '.join(failures)}")
33+
34+
35+
if __name__ == "__main__":
36+
main()

.github/workflows/hitomi-db.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Hitomi DB Build
2+
3+
on:
4+
schedule:
5+
- cron: '0 3 * * 1' # UTC Monday 03:00
6+
workflow_dispatch:
7+
inputs:
8+
force_full:
9+
description: 'Force full scrape (ignore previous errors)'
10+
required: false
11+
default: 'false'
12+
type: boolean
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 30
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.12'
28+
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
python -m pip install httpx[http2] lxml loguru PyYAML polib PyQt5
33+
34+
- name: Scrape dataset
35+
id: scrape
36+
continue-on-error: true
37+
run: |
38+
python -m utils.website.hitomi.scape_dataset \
39+
--db-path assets/hitomi.db \
40+
--workers 2 \
41+
--max-retries 5 \
42+
--timeout 15
43+
44+
- name: Retry failed (if first attempt had errors)
45+
id: retry
46+
if: steps.scrape.outcome == 'failure'
47+
run: |
48+
python -m utils.website.hitomi.scape_dataset \
49+
--db-path assets/hitomi.db \
50+
--workers 1 \
51+
--max-retries 8 \
52+
--timeout 20 \
53+
--only-failed
54+
55+
- name: Quality gate
56+
run: python .github/scripts/hitomi_db_gate.py assets/hitomi.db
57+
58+
- name: Generate manifest
59+
run: |
60+
python .github/scripts/generate_hitomi_manifest.py \
61+
assets/hitomi.db \
62+
--output assets/hitomi-manifest.json
63+
64+
- name: Upload to release
65+
env:
66+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67+
run: |
68+
TAG="preset"
69+
70+
# Upload assets to existing release (or create if not exists)
71+
if gh release view "$TAG" &>/dev/null; then
72+
# Delete old assets then re-upload
73+
gh release delete-asset "$TAG" hitomi.db --yes 2>/dev/null || true
74+
gh release delete-asset "$TAG" hitomi-manifest.json --yes 2>/dev/null || true
75+
gh release upload "$TAG" \
76+
assets/hitomi.db \
77+
assets/hitomi-manifest.json
78+
else
79+
gh release create "$TAG" \
80+
--title "Preset Assets" \
81+
--notes "Auto-managed preset assets (hitomi.db, etc.)" \
82+
--latest=false \
83+
assets/hitomi.db \
84+
assets/hitomi-manifest.json
85+
fi

.github/workflows/release.yml

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,34 +34,7 @@ jobs:
3434
uv venv .venv-smoke
3535
source .venv-smoke/bin/activate
3636
uv pip install dist/*.whl
37-
python - <<'PY'
38-
import os
39-
import subprocess
40-
import sys
41-
42-
env = os.environ.copy()
43-
tests = [
44-
("entrypoint import", [sys.executable, "-c", "import CGS"]),
45-
("check mode startup", [sys.executable, "-c", "import CGS; CGS.start()"]),
46-
("cli help", ["cgs-cli", "--help"]),
47-
]
48-
49-
failures = []
50-
for name, cmd in tests:
51-
print(f"::group::{name}")
52-
result = subprocess.run(cmd, env=env, text=True, capture_output=True)
53-
if result.stdout:
54-
print(result.stdout)
55-
if result.stderr:
56-
print(result.stderr, file=sys.stderr)
57-
print(f"exit_code={result.returncode}")
58-
print("::endgroup::")
59-
if result.returncode != 0:
60-
failures.append(name)
61-
62-
if failures:
63-
raise SystemExit(f"Smoke tests failed: {', '.join(failures)}")
64-
PY
37+
uv run .github/scripts/smoke_wheel.py
6538
6639
- name: Publish to PyPI
6740
run: uv publish

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ custom/start/*
186186
*.ico
187187
*.qrc
188188
src/
189+
!GUI/src/
190+
GUI/src/*
189191
!GUI/src/preview_format/
190192
!GUI/src/material_ct.py
191193
!GUI/src/__init__.py
@@ -204,7 +206,7 @@ version.json
204206
# self/conf
205207
gitee_t.json
206208
record.db
207-
hitomi.db
209+
hitomi.db*
208210
# self/desc created html
209211
desc.html
210212
docs/*.html
@@ -214,6 +216,8 @@ _bug_log
214216
# AI
215217
.cursor
216218
.claude
219+
.codex
220+
.gemini
217221
.kilocode
218222
/plans
219223
.ace-tool/

0 commit comments

Comments
 (0)