From: mar77i Date: Sat, 24 Jan 2026 15:38:45 +0000 (+0100) Subject: add ungoogled-chromium. fix some things. consolidate a wee bit X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;ds=inline;p=localapps add ungoogled-chromium. fix some things. consolidate a wee bit --- diff --git a/apps/librewolf.py b/apps/librewolf.py index bdeec22..4285ebf 100644 --- a/apps/librewolf.py +++ b/apps/librewolf.py @@ -14,7 +14,7 @@ from localapps import AppBase, find_html_attr class DownloadFinder(HTMLParser): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.attention = False + self.attention = 0 self.links = [] @classmethod @@ -28,16 +28,18 @@ class DownloadFinder(HTMLParser): def handle_starttag(self, tag, attrs): tag = tag.upper() - if tag == "DETAILS" and "download" in self.get_classes(attrs): - self.attention = True - elif self.attention and tag == "A": + if tag == "DETAILS" and ( + self.attention > 0 or "download" in self.get_classes(attrs) + ): + self.attention += 1 + elif self.attention > 0 and tag == "A": href = find_html_attr(attrs, "href") if href and not "archive-link" in self.get_classes(attrs): self.links.append(href) def handle_endtag(self, tag): - if self.attention and tag.upper() == "DETAILS": - self.attention = False + if tag.upper() == "DETAILS" and self.attention > 0: + self.attention -= 1 class LibreWolfApp(AppBase): @@ -49,7 +51,7 @@ class LibreWolfApp(AppBase): ICON_URL = ( "https://aur.archlinux.org/cgit/aur.git/plain/default192x192.png?h=librewolf" ) - BIN_PATH = AppBase.APPS_DIR / "librewolf" / "librewolf" + BIN_PATH = AppBase.APPS_DIR / NAME.lower() / "librewolf" @classmethod def get_latest_version(cls): @@ -104,12 +106,14 @@ class LibreWolfApp(AppBase): ) continue elif not suffix.endswith("sum"): - print(f"Ignoring {check_url[check_url.rfind('/') + 1:]}", file=sys.stderr) + print( + f"Ignoring {check_url[check_url.rfind('/') + 1:]}", file=sys.stderr + ) continue m = getattr(hashlib, suffix[1:-3])(content) assert m.hexdigest() == urlopen(check_url).read().decode().strip() assert AppBase.APPS_DIR.is_dir() or not AppBase.APPS_DIR.exists() - app_dir = (AppBase.APPS_DIR / cls.NAME.lower()).absolute() + app_dir = cls.BIN_PATH.parent app_dir.mkdir(0o755, parents=True, exist_ok=True) run( ["tar", "xJ", "--strip-components=1", "-C", str(app_dir)], @@ -122,12 +126,14 @@ class LibreWolfApp(AppBase): ) with (app_dir / "default192x192.png").open("wb") as fh: fh.write(urlopen(cls.ICON_URL).read()) - with (cls.get_xdg_home() / "applications" / "librewolf.desktop").open("wt") as fh: + with ( + cls.get_xdg_home() / "applications" / "librewolf.desktop" + ).open("wt") as fh: fh.write(desktop_file) @classmethod def uninstall(cls): - rmtree((AppBase.APPS_DIR / cls.NAME.lower()).absolute()) - ( - cls.get_xdg_home() / "applications" / "librewolf.desktop" - ).unlink(missing_ok=True) + rmtree(cls.BIN_PATH.parent) + (cls.get_xdg_home() / "applications" / "librewolf.desktop").unlink( + missing_ok=True + ) diff --git a/apps/materialgram.py b/apps/materialgram.py index 047b24d..912d468 100644 --- a/apps/materialgram.py +++ b/apps/materialgram.py @@ -33,15 +33,16 @@ class FragmentFinder(HTMLParser): class DownloadFinder(HTMLParser): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.attention = False - self.spans = False + self.attention = 0 + self.spans = 0 self.items = [] def handle_starttag(self, tag, attrs): tag = tag.upper() - if not self.attention: - if tag == "UL": - self.attention = True + if tag == "UL": + self.attention += 1 + elif self.attention == 0: + return elif tag == "A": href = find_html_attr(attrs, "href") if href: @@ -52,25 +53,26 @@ class DownloadFinder(HTMLParser): } ) elif tag == "SPAN": - self.spans = True + self.spans += 1 def handle_data(self, data): - if self.spans: + if self.spans > 0: data = data.strip() if data: self.items[-1]["spans"].append(data) def handle_endtag(self, tag): if self.spans and tag.upper() == "SPAN": - self.spans = False + self.spans -= 1 elif self.attention and tag.upper() == "UL": - self.attention = False + self.spans = 0 + self.attention -= 1 class Materialgram(AppBase): NAME = "Materialgram" RELEASE_URL = "https://github.com/kukuruzka165/materialgram/releases.atom" - BIN_PATH = AppBase.APPS_DIR / "materialgram" / "usr" / "bin" / "materialgram" + BIN_PATH = AppBase.APPS_DIR / NAME.lower() / "usr" / "bin" / "materialgram" NS = {"": "http://www.w3.org/2005/Atom"} @classmethod @@ -111,7 +113,7 @@ class Materialgram(AppBase): desktop_file_paths = [ path for path in ( - (AppBase.APPS_DIR / cls.NAME.lower()).absolute() + cls.BIN_PATH.parents[2] / "usr" / "share" / "applications" @@ -162,7 +164,7 @@ class Materialgram(AppBase): algo, hexdigest = item["spans"][1].split(":") m = getattr(hashlib, algo)(content) assert m.hexdigest() == hexdigest - app_dir = (AppBase.APPS_DIR / cls.NAME.lower()).absolute() + app_dir = cls.BIN_PATH.parents[2] app_dir.mkdir(0o755, parents=True, exist_ok=True) run(["tar", "xz", "-C", str(app_dir)], input=content) desktop_file_path = cls.get_desktop_file_path() @@ -208,7 +210,7 @@ class Materialgram(AppBase): @classmethod def uninstall(cls): - ( - cls.get_xdg_home() / "applications" / cls.get_desktop_file_path().name - ).unlink(missing_ok=True) - rmtree((AppBase.APPS_DIR / cls.NAME.lower()).absolute()) + (cls.get_xdg_home() / "applications" / cls.get_desktop_file_path().name).unlink( + missing_ok=True + ) + rmtree(cls.BIN_PATH.parents[2]) diff --git a/apps/pycharm.py b/apps/pycharm_community.py similarity index 88% rename from apps/pycharm.py rename to apps/pycharm_community.py index ff83a82..8a0915e 100644 --- a/apps/pycharm.py +++ b/apps/pycharm_community.py @@ -16,12 +16,12 @@ class DesktopConfigParser(ConfigParser): class PyCharm(AppBase): - NAME = "PyCharm" + NAME = "PyCharm-Community" RELEASE_URL = ( "https://data.services.jetbrains.com/products/releases" "?code=PCP&latest=true&type=release" ) - BIN_PATH = AppBase.APPS_DIR / "pycharm" / "bin" / "pycharm" + BIN_PATH = AppBase.APPS_DIR / NAME.lower() / "bin" / "pycharm" @classmethod def get_latest_version(cls): @@ -51,7 +51,7 @@ class PyCharm(AppBase): checksum = checksum[:pos] m = getattr(hashlib, checksum_url[checksum_url.rfind(".") + 1:])(content) assert m.hexdigest() == checksum - app_dir = (AppBase.APPS_DIR / cls.NAME.lower()).absolute() + app_dir = cls.BIN_PATH.parents[1] app_dir.mkdir(0o755, parents=True, exist_ok=True) run(["tar", "xz", "--strip-components=1", "-C", str(app_dir)], input=content) config_parser = DesktopConfigParser(interpolation=None) @@ -73,7 +73,7 @@ class PyCharm(AppBase): @classmethod def uninstall(cls): - rmtree((AppBase.APPS_DIR / cls.NAME.lower()).absolute()) - ( - cls.get_xdg_home() / "applications" / "pycharm.desktop" - ).unlink(missing_ok=True) + rmtree(cls.BIN_PATH.parents[1]) + (cls.get_xdg_home() / "applications" / "pycharm.desktop").unlink( + missing_ok=True + ) diff --git a/apps/ungoogled_chromium.py b/apps/ungoogled_chromium.py new file mode 100644 index 0000000..40177bc --- /dev/null +++ b/apps/ungoogled_chromium.py @@ -0,0 +1,146 @@ +import hashlib +import sysconfig +from html.parser import HTMLParser +from shutil import rmtree +from subprocess import check_output, run +from urllib.request import urlopen +from xml.etree import ElementTree + +from localapps import AppBase, find_html_attr + + +class DownloadFinder(HTMLParser): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.h2 = False + self.attention = False + self.data = [] + + def handle_starttag(self, tag, attrs): + tag = tag.upper() + if tag == "H2": + self.h2 = True + elif self.attention: + if tag == "A": + self.data[-1].append(find_html_attr(attrs, "href")) + elif tag == "LI": + self.data.append([]) + + def handle_data(self, data): + if self.h2 and data.strip().upper() == "DOWNLOADS": + self.attention = True + elif self.attention and self.data and data.strip(): + self.data[-1].append(data) + + def handle_endtag(self, tag): + tag = tag.upper() + if self.h2 and tag == "H2": + self.h2 = False + + +class UngoogledChromium(AppBase): + NAME = "Ungoogled-Chromium" + RELEASE_URL = ( + "https://ungoogled-software.github.io/ungoogled-chromium-binaries/feed.xml" + ) + BIN_PATH = AppBase.APPS_DIR / NAME.lower() / "chrome" + NS = {"": "http://www.w3.org/2005/Atom"} + PLATFORMS = {"linux-x86_64": "Portable Linux 64-bit"} + DESKTOP_URL = ( + "https://raw.githubusercontent.com/ungoogled-software" + "/ungoogled-chromium-portablelinux/refs/heads/master/package" + "/ungoogled-chromium.desktop" + ) + ICON_URLS = ( + ( + "https://github.com/chromium/chromium/raw/refs/heads/main/chrome/app/theme" + "/chromium/product_logo_{size}.png" + ), + ( + "https://github.com/chromium/chromium/raw/refs/heads/main/chrome/app/theme" + "/default_100_percent/chromium/product_logo_{size}.png" + ), + ) + + @classmethod + def get_latest_version(cls): + return dict( + entry.find("title", cls.NS).text.split(":", 1) + for entry in ElementTree.parse( + urlopen(cls.RELEASE_URL) + ).getroot().findall("entry", cls.NS) + )[cls.PLATFORMS[sysconfig.get_platform()]].strip() + + @classmethod + def get_installed_version(cls): + if cls.has_executable(): + output = check_output([cls.BIN_PATH, "--version"], text=True).strip() + return output.split(maxsplit=1)[1] + return None + + @classmethod + def install(cls): + url = dict( + (key, url) + for key, url in ( + ( + entry.find("title", cls.NS).text.split(":", 1)[0], + entry.find("link", cls.NS).attrib["href"] + ) + for entry in ElementTree.parse( + urlopen(cls.RELEASE_URL) + ).getroot().findall("entry", cls.NS) + ) + )[cls.PLATFORMS[sysconfig.get_platform()]] + download_finder = DownloadFinder() + download_finder.feed(urlopen(url).read().decode()) + content = cls.cached_download(download_finder.data[0][0]) + for algo, digest in ( + (a.strip().strip(":").lower(), b) for a, b in download_finder.data[1:] + ): + m = getattr(hashlib, algo)(content) + assert m.hexdigest() == digest + app_dir = cls.BIN_PATH.parent + app_dir.mkdir(0o755, parents=True, exist_ok=True) + run( + ["tar", "xJ", "--strip-components=1", "-C", str(app_dir)], + check=True, + input=content + ) + for size in (16, 24, 32, 48, 64, 128, 256): + icon_path = ( + cls.get_xdg_home() + / "icons" + / "hicolor" + / f"{size}x{size}" + / "apps" + / "chromium.png" + ) + icon_path.parent.mkdir(0o755, parents = True, exist_ok=True) + with icon_path.open("wb") as fh: + fh.write( + urlopen(cls.ICON_URLS[size in (16, 32)].format(size=size)).read() + ) + desktop_file = urlopen(cls.DESKTOP_URL).read().decode().replace( + "Exec=chromium", f"Exec={app_dir / 'chrome'}" + ) + with ( + cls.get_xdg_home() / "applications" / "ungoogled-chromium.desktop" + ).open("wt") as fh: + fh.write(desktop_file) + + @classmethod + def uninstall(cls): + rmtree(cls.BIN_PATH.parent) + ( + cls.get_xdg_home() / "applications" / "ungoogled-chromium.desktop" + ).unlink(missing_ok=True) + for size in (16, 24, 32, 48, 64, 128, 256): + ( + cls.get_xdg_home() + / "icons" + / "hicolor" + / f"{size}x{size}" + / "apps" + / "chromium.png" + ).unlink(missing_ok=True) diff --git a/localapps.py b/localapps.py index 9e0a9f2..3fb2691 100755 --- a/localapps.py +++ b/localapps.py @@ -16,6 +16,7 @@ class AppBase: registry: list[type[AppBase]] = [] def __init_subclass__(cls, *args, **kwargs): + assert cls.NAME.upper() not in (c.NAME.upper() for c in cls.registry) cls.registry.append(cls) assert cls.NAME is not None