]> git.mar77i.info Git - localapps/commitdiff
add ungoogled-chromium. fix some things. consolidate a wee bit master
authormar77i <mar77i@protonmail.ch>
Sat, 24 Jan 2026 15:38:45 +0000 (16:38 +0100)
committermar77i <mar77i@protonmail.ch>
Sat, 24 Jan 2026 15:42:29 +0000 (16:42 +0100)
apps/librewolf.py
apps/materialgram.py
apps/pycharm_community.py [moved from apps/pycharm.py with 88% similarity]
apps/ungoogled_chromium.py [new file with mode: 0644]
localapps.py

index bdeec2202ff81808273a47499788350cd0f68cb0..4285ebf9ef46368f68e78ee0f2bacb5a97e05c95 100644 (file)
@@ -14,7 +14,7 @@ from localapps import AppBase, find_html_attr
 class DownloadFinder(HTMLParser):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 class DownloadFinder(HTMLParser):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.attention = False
+        self.attention = 0
         self.links = []
 
     @classmethod
         self.links = []
 
     @classmethod
@@ -28,16 +28,18 @@ class DownloadFinder(HTMLParser):
 
     def handle_starttag(self, tag, attrs):
         tag = tag.upper()
 
     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):
             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):
 
 
 class LibreWolfApp(AppBase):
@@ -49,7 +51,7 @@ class LibreWolfApp(AppBase):
     ICON_URL = (
         "https://aur.archlinux.org/cgit/aur.git/plain/default192x192.png?h=librewolf"
     )
     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):
 
     @classmethod
     def get_latest_version(cls):
@@ -104,12 +106,14 @@ class LibreWolfApp(AppBase):
                 )
                 continue
             elif not suffix.endswith("sum"):
                 )
                 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()
                 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)],
         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 (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):
             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
+        )
index 047b24d687af01eeaae75ce618d763482d68e6c4..912d46886d3d42de3e57f5d1451ce8c77e0d85da 100644 (file)
@@ -33,15 +33,16 @@ class FragmentFinder(HTMLParser):
 class DownloadFinder(HTMLParser):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 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()
         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:
         elif tag == "A":
             href = find_html_attr(attrs, "href")
             if href:
@@ -52,25 +53,26 @@ class DownloadFinder(HTMLParser):
                     }
                 )
         elif tag == "SPAN":
                     }
                 )
         elif tag == "SPAN":
-            self.spans = True
+            self.spans += 1
 
     def handle_data(self, data):
 
     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":
             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":
         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"
 
 
 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
     NS = {"": "http://www.w3.org/2005/Atom"}
 
     @classmethod
@@ -111,7 +113,7 @@ class Materialgram(AppBase):
         desktop_file_paths = [
             path
             for path in (
         desktop_file_paths = [
             path
             for path in (
-                (AppBase.APPS_DIR / cls.NAME.lower()).absolute()
+                cls.BIN_PATH.parents[2]
                 / "usr"
                 / "share"
                 / "applications"
                 / "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
         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()
         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):
 
     @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])
similarity index 88%
rename from apps/pycharm.py
rename to apps/pycharm_community.py
index ff83a8206c50ade4ab3a285b487c14a1283b381e..8a0915ec3829364a554eeb9af4df843d33cda773 100644 (file)
@@ -16,12 +16,12 @@ class DesktopConfigParser(ConfigParser):
 
 
 class PyCharm(AppBase):
 
 
 class PyCharm(AppBase):
-    NAME = "PyCharm"
+    NAME = "PyCharm-Community"
     RELEASE_URL = (
         "https://data.services.jetbrains.com/products/releases"
         "?code=PCP&latest=true&type=release"
     )
     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):
 
     @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
             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)
         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):
 
     @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 (file)
index 0000000..40177bc
--- /dev/null
@@ -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)
index 9e0a9f2582f2f340eff775f45f39254c608c52c1..3fb26915ce7a65b8ea6e5f295da11c0b352f4ee2 100755 (executable)
@@ -16,6 +16,7 @@ class AppBase:
     registry: list[type[AppBase]] = []
 
     def __init_subclass__(cls, *args, **kwargs):
     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
 
         cls.registry.append(cls)
         assert cls.NAME is not None