MY BRAND
fix(update): apply Gitea archive under dazedtl root - Resolve archive root dynamically instead of a stale slug - Fail when the zip has no installable files - Install into PROJECT_ROOT and cover archive-root regressions
This commit is contained in:
parent
4d1b01c870
commit
7f7348a5a6
2 changed files with 95 additions and 5 deletions
34
gui/main.py
34
gui/main.py
|
|
@ -78,7 +78,9 @@ class UpdateThread(QThread):
|
|||
REPO_OWNER = "dazed"
|
||||
REPO_SLUG = "dazed-mtl-tool"
|
||||
REPO_BRANCH = "main"
|
||||
ARCHIVE_ROOT = "dazed-mtl-tool"
|
||||
# Gitea names the archive top folder after the repo display name (currently
|
||||
# "dazedtl"). Resolve dynamically so a rename cannot silently no-op again.
|
||||
ARCHIVE_ROOT = "dazedtl"
|
||||
SHA_FILE = str(LAST_UPDATE_SHA_PATH)
|
||||
|
||||
# Top-level paths that should never be touched during update
|
||||
|
|
@ -117,6 +119,25 @@ class UpdateThread(QThread):
|
|||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def resolve_archive_root(cls, extract_dir: Path) -> Path:
|
||||
"""Locate the single top-level folder inside an extracted archive.
|
||||
|
||||
Prefers ``ARCHIVE_ROOT`` when present; otherwise accepts exactly one
|
||||
subdirectory (Gitea zip layout). Raises if the layout is unexpected.
|
||||
"""
|
||||
preferred = extract_dir / cls.ARCHIVE_ROOT
|
||||
if preferred.is_dir():
|
||||
return preferred
|
||||
dirs = sorted(p for p in extract_dir.iterdir() if p.is_dir())
|
||||
if len(dirs) == 1:
|
||||
return dirs[0]
|
||||
found = [p.name for p in dirs]
|
||||
raise FileNotFoundError(
|
||||
f"Could not find update archive root under {extract_dir} "
|
||||
f"(expected {cls.ARCHIVE_ROOT!r}; found {found})"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _fmt_bytes(num: int) -> str:
|
||||
if num >= 1024 * 1024:
|
||||
|
|
@ -211,16 +232,21 @@ class UpdateThread(QThread):
|
|||
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||
zf.extractall(tmp)
|
||||
|
||||
extracted = tmp / self.ARCHIVE_ROOT
|
||||
root = Path(".").resolve()
|
||||
extracted = self.resolve_archive_root(tmp)
|
||||
root = PROJECT_ROOT.resolve()
|
||||
|
||||
install_files = [
|
||||
src
|
||||
for src in extracted.rglob("*")
|
||||
if src.is_file() and self.should_install(src.relative_to(extracted))
|
||||
]
|
||||
total_files = max(len(install_files), 1)
|
||||
if not install_files:
|
||||
raise RuntimeError(
|
||||
"Update archive contained no installable files "
|
||||
f"(root={extracted.name!r})"
|
||||
)
|
||||
|
||||
total_files = len(install_files)
|
||||
self.progress.emit("Applying", 85, "Installing updated files…")
|
||||
for index, src in enumerate(install_files, start=1):
|
||||
rel = src.relative_to(extracted)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,67 @@ class UpdateThreadInstallFilterTests(unittest.TestCase):
|
|||
self.assertFalse(UpdateThread.should_install(Path(rel)))
|
||||
|
||||
|
||||
class UpdateThreadArchiveRootTests(unittest.TestCase):
|
||||
"""Gitea zips nest files under a single top folder (repo display name)."""
|
||||
|
||||
def test_prefers_configured_archive_root(self):
|
||||
from gui.main import UpdateThread
|
||||
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
preferred = base / UpdateThread.ARCHIVE_ROOT
|
||||
preferred.mkdir()
|
||||
(base / "other").mkdir()
|
||||
self.assertEqual(UpdateThread.resolve_archive_root(base), preferred)
|
||||
|
||||
def test_falls_back_to_single_subdirectory(self):
|
||||
from gui.main import UpdateThread
|
||||
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
only = base / "renamed-repo-folder"
|
||||
only.mkdir()
|
||||
(only / "gui").mkdir()
|
||||
(only / "gui" / "main.py").write_text("# ok", encoding="utf-8")
|
||||
self.assertEqual(UpdateThread.resolve_archive_root(base), only)
|
||||
|
||||
def test_raises_when_archive_root_missing(self):
|
||||
from gui.main import UpdateThread
|
||||
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
(base / "a").mkdir()
|
||||
(base / "b").mkdir()
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
UpdateThread.resolve_archive_root(base)
|
||||
|
||||
def test_stale_archive_root_name_would_have_installed_nothing(self):
|
||||
"""Regression: wrong ARCHIVE_ROOT used to report success with 0 files."""
|
||||
from gui.main import UpdateThread
|
||||
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
real = base / "dazedtl"
|
||||
real.mkdir()
|
||||
(real / "gui").mkdir()
|
||||
(real / "gui" / "main.py").write_text("print('hi')\n", encoding="utf-8")
|
||||
(real / "data").mkdir()
|
||||
(real / "data" / "help").mkdir()
|
||||
(real / "data" / "help" / "00-welcome.md").write_text("# hi\n", encoding="utf-8")
|
||||
|
||||
# Old hardcoded slug before the DazedTL rebrand.
|
||||
stale = base / "dazed-mtl-tool"
|
||||
self.assertFalse(stale.exists())
|
||||
self.assertEqual(list(stale.rglob("*")), [])
|
||||
|
||||
extracted = UpdateThread.resolve_archive_root(base)
|
||||
install_files = [
|
||||
src
|
||||
for src in extracted.rglob("*")
|
||||
if src.is_file() and UpdateThread.should_install(src.relative_to(extracted))
|
||||
]
|
||||
self.assertGreaterEqual(len(install_files), 2)
|
||||
|
||||
class ShippedDataTrackingTests(unittest.TestCase):
|
||||
"""Guide/help and other shipped data/ files must be git-trackable.
|
||||
|
||||
|
|
@ -417,8 +478,11 @@ class MaintainerRefreshTests(unittest.TestCase):
|
|||
with patch.object(forge, "_fetch_plugins", return_value=plugin):
|
||||
self.assertTrue(forge.refresh_forge_plugins(log_fn=None))
|
||||
|
||||
self.assertEqual((base / "Forge_MV.js").read_bytes(), plugin)
|
||||
# Maintainer refresh updates MZ modern only; MV stays on the legacy bundle.
|
||||
self.assertEqual((base / "Forge_MZ.js").read_bytes(), plugin)
|
||||
self.assertEqual((upstream / "Forge_MZ.js").read_bytes(), plugin)
|
||||
self.assertFalse((base / "Forge_MV.js").exists())
|
||||
self.assertFalse((upstream / "Forge_MV.js").exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Reference in a new issue