From 9f0dab208616ac5cfee713ea5823cb5f2a6f1ebc Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 15 Oct 2026 12:15:31 +0200 Subject: feat(updates): Slackware upgrades, new and removed packages Compared mirror against installed, as upgrade-all does, not against slackpkg's local ChangeLog: `slackpkg update` replaces that without installing anything, so a diff against it reads clean after an update that was never applied. New and removed come from the mirror's ChangeLog, the newest Added or Removed line deciding, so a package added and later upgraded still counts as new. Blacklisted build tags and directories are skipped, as slackpkg skips them. Co-Authored-By: Claude Sonnet 5.5 --- desktop/modules/updates/test_updatesctl.py | 5 ++ desktop/modules/updates/updatesctl | 88 +++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/desktop/modules/updates/test_updatesctl.py b/desktop/modules/updates/test_updatesctl.py index b9e3881..2bfe1a8 100644 --- a/desktop/modules/updates/test_updatesctl.py +++ b/desktop/modules/updates/test_updatesctl.py @@ -178,5 +178,10 @@ assert rows(d["apps"]) == { "GIMP": ("flatpak", "3.0.4", "3.0.6"), }, d["apps"] +sw = d["slackware"] +assert sw["upgrade"] == [{"name": "glibc", "from": "2.44-3", "to": "2.44-4"}], sw +assert sw["new"] == [{"name": "libtatsu", "version": "1.0.5-1"}], sw +assert sw["removed"] == [{"name": "dhcp", "date": "2026-10-14"}], sw + shutil.rmtree(tmp) print("ok") diff --git a/desktop/modules/updates/updatesctl b/desktop/modules/updates/updatesctl index 413bbcd..835f3d0 100755 --- a/desktop/modules/updates/updatesctl +++ b/desktop/modules/updates/updatesctl @@ -260,6 +260,91 @@ def tracked(installed, errors): return out +def blacklist(): + """slackpkg's blacklist as regexes, its surrounding quotes dropped. + + ponytail: a pattern blocks when it occurs anywhere in location/full + name, so a bare "dhcp" would also block "dhcpcd"; slackpkg itself is + stricter. Fine for a list of build tags and directories.""" + out = [] + for line in read(sysp("/etc/slackpkg/blacklist")).splitlines(): + p = line.strip().strip('"') + if p and not p.startswith("#"): + out.append(re.compile(p)) + return out + + +def blocked(bl, path): + return any(p.search(path) for p in bl) + + +DATE = re.compile(r"^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d+ \d\d:\d\d:\d\d UTC \d{4}$") +CLPKG = re.compile(r"^(\S+?)\.t[gx]z:\s+(Added|Removed)\.") + + +def changelog(text): + """Package name -> (Added|Removed, date) from its newest such line. + + The ChangeLog is newest first, so the first line seen wins. Other + actions are skipped: a package added and later upgraded is still new.""" + seen, date = {}, "" + for line in text.splitlines(): + if DATE.match(line): + date = time.strftime("%Y-%m-%d", + time.strptime(line, "%a %b %d %H:%M:%S UTC %Y")) + continue + m = CLPKG.match(line) + if m: + name = split(m.group(1).rsplit("/", 1)[-1])[0] + seen.setdefault(name, (m.group(2), date)) + return seen + + +def mirror_dir(): + for line in read(sysp("/etc/slackpkg/mirrors")).splitlines(): + line = line.strip() + if line and not line.startswith("#"): + return local_dir(line) + return None + + +def verbuild(full): + _, v, _, b = split(full) + return f"{v}-{b}" + + +def slackware(installed, errors): + out = {"upgrade": [], "new": [], "removed": []} + d = mirror_dir() + if d is None: + errors["mirror"] = "not a local mirror" + return out + pk, cl = nfs_read(d / "PACKAGES.TXT"), nfs_read(d / "ChangeLog.txt") + if pk is None or cl is None: + errors["mirror"] = "unreachable" + return out + + bl = blacklist() + mirror = {} + for loc, full in packages_txt(pk): + if not blocked(bl, f"{loc}/{full}"): + mirror.setdefault(split(full)[0], full) + every = {split(f)[0] for f in installed if f.count("-") >= 3} + mine = {split(f)[0]: f for f in installed + if f.count("-") >= 3 and not blocked(bl, f)} + + for name, full in sorted(mine.items()): + if name in mirror and mirror[name] != full: + out["upgrade"].append({"name": name, "from": verbuild(full), + "to": verbuild(mirror[name])}) + for name, (act, date) in sorted(changelog(cl).items()): + if act == "Added" and name in mirror and name not in every: + out["new"].append({"name": name, "version": verbuild(mirror[name])}) + elif act == "Removed" and name in mine and name not in mirror: + out["removed"].append({"name": name, "date": date}) + return out + + def main(): errors = {} if "--refresh" in sys.argv[1:]: @@ -274,7 +359,8 @@ def main(): installed = os.listdir(sysp("/var/log/packages")) except OSError: installed = [] - doc = {"checked": int(NEWVER.stat().st_mtime) if NEWVER.exists() else 0} + doc = {"checked": int(NEWVER.stat().st_mtime) if NEWVER.exists() else 0, + "slackware": slackware(installed, errors)} doc.update(tracked(installed, errors)) doc["errors"] = errors json.dump(doc, sys.stdout) -- cgit v1.2.3