about summary refs log tree commit diff
path: root/nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py')
-rwxr-xr-xnixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py109
1 files changed, 62 insertions, 47 deletions
diff --git a/nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py b/nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py
index d1fdfc1f1a53..9117ea270407 100755
--- a/nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py
+++ b/nixpkgs/pkgs/desktops/gnome/extensions/update-extensions.py
@@ -1,16 +1,15 @@
 #!/usr/bin/env nix-shell
 #!nix-shell -I nixpkgs=../../../.. -i python3 -p python3
 
+import base64
 import json
-import urllib.request
-import urllib.error
-from typing import List, Dict, Optional, Any, Tuple
 import logging
-from operator import itemgetter
 import subprocess
-import zipfile
-import io
-import base64
+import urllib.error
+import urllib.request
+from operator import itemgetter
+from pathlib import Path
+from typing import List, Dict, Optional, Any, Tuple
 
 # We don't want all those deprecated legacy extensions
 # Group extensions by GNOME "major" version for compatibility reasons
@@ -21,14 +20,12 @@ supported_versions = {
     "42": "42",
 }
 
-
-# Some type alias to increase readility of complex compound types
+# Some type alias to increase readability of complex compound types
 PackageName = str
 ShellVersion = str
 Uuid = str
 ExtensionVersion = int
 
-
 # Keep track of all names that have been used till now to detect collisions.
 # This works because we deterministically process all extensions in historical order
 # The outer dict level is the shell version, as we are tracking duplicates only per same Shell version.
@@ -37,6 +34,8 @@ package_name_registry: Dict[ShellVersion, Dict[PackageName, List[Uuid]]] = {}
 for shell_version in supported_versions.keys():
     package_name_registry[shell_version] = {}
 
+updater_dir_path = Path(__file__).resolve().parent
+
 
 def fetch_extension_data(uuid: str, version: str) -> Tuple[str, str]:
     """
@@ -48,28 +47,28 @@ def fetch_extension_data(uuid: str, version: str) -> Tuple[str, str]:
     uuid = uuid.replace("@", "")
     url: str = f"https://extensions.gnome.org/extension-data/{uuid}.v{version}.shell-extension.zip"
 
-    # Yes, we download that file three times:
+    # Download extension and add the zip content to nix-store
+    process = subprocess.run(
+        ["nix-prefetch-url", "--unpack", "--print-path", url], capture_output=True, text=True
+    )
 
-    # The first time is for the maintainter, so they may have a personal backup to fix potential issues
-    # subprocess.run(
-    #     ["wget", url], capture_output=True, text=True
-    # )
+    lines = process.stdout.splitlines()
 
-    # The second time, we extract the metadata.json because we need it too
-    with urllib.request.urlopen(url) as response:
-        data = zipfile.ZipFile(io.BytesIO(response.read()), 'r')
-        metadata = base64.b64encode(data.read('metadata.json')).decode()
+    # Get hash from first line of nix-prefetch-url output
+    hash = lines[0].strip()
 
-    # The third time is to get the file into the store and to get its hash
-    hash = subprocess.run(
-        ["nix-prefetch-url", "--unpack", url], capture_output=True, text=True
-    ).stdout.strip()
+    # Get path from second line of nix-prefetch-url output
+    path = Path(lines[1].strip())
+
+    # Get metadata.json content from nix-store
+    with open(path / "metadata.json", "r") as out:
+        metadata = base64.b64encode(out.read().encode("ascii")).decode()
 
     return hash, metadata
 
 
 def generate_extension_versions(
-    extension_version_map: Dict[ShellVersion, ExtensionVersion], uuid: str
+        extension_version_map: Dict[ShellVersion, ExtensionVersion], uuid: str
 ) -> Dict[ShellVersion, Dict[str, str]]:
     """
     Takes in a mapping from shell versions to extension versions and transforms it the way we need it:
@@ -77,7 +76,9 @@ def generate_extension_versions(
     - Filter out versions that only support old GNOME versions
     - Download the extension and hash it
     """
-    extension_versions: Dict[ShellVersion, Dict[str, str]] = {}
+
+    # Determine extension version per shell version
+    extension_versions: Dict[ShellVersion, ExtensionVersion] = {}
     for shell_version, version_prefix in supported_versions.items():
         # Newest compatible extension version
         extension_version: Optional[int] = max(
@@ -91,19 +92,32 @@ def generate_extension_versions(
         # Extension is not compatible with this GNOME version
         if not extension_version:
             continue
+
+        extension_versions[shell_version] = extension_version
+
+    # Download information once for all extension versions chosen above
+    extension_info_cache: Dict[ExtensionVersion, Tuple[str, str]] = {}
+    for extension_version in sorted(set(extension_versions.values())):
         logging.debug(
-            f"[{shell_version}] Downloading '{uuid}' v{extension_version}"
+            f"[{uuid}] Downloading v{extension_version}"
         )
-        sha256, metadata = fetch_extension_data(uuid, str(extension_version))
-        extension_versions[shell_version] = {
+        extension_info_cache[extension_version] = \
+            fetch_extension_data(uuid, str(extension_version))
+
+    # Fill map
+    extension_versions_full: Dict[ShellVersion, Dict[str, str]] = {}
+    for shell_version, extension_version in extension_versions.items():
+        sha256, metadata = extension_info_cache[extension_version]
+
+        extension_versions_full[shell_version] = {
             "version": str(extension_version),
             "sha256": sha256,
             # The downloads are impure, their metadata.json may change at any time.
-            # Thus, be back it up / pin it to remain deterministic
+            # Thus, we back it up / pin it to remain deterministic
             # Upstream issue: https://gitlab.gnome.org/Infrastructure/extensions-web/-/issues/137
             "metadata": metadata,
         }
-    return extension_versions
+    return extension_versions_full
 
 
 def pname_from_url(url: str) -> Tuple[str, str]:
@@ -112,7 +126,7 @@ def pname_from_url(url: str) -> Tuple[str, str]:
     """
 
     url = url.split("/")  # type: ignore
-    return (url[3], url[2])
+    return url[3], url[2]
 
 
 def process_extension(extension: Dict[str, Any]) -> Optional[Dict[str, Any]]:
@@ -136,7 +150,7 @@ def process_extension(extension: Dict[str, Any]) -> Optional[Dict[str, Any]]:
                    Don't make any assumptions on it, and treat it like an opaque string!
             "link" follows the following schema: "/extension/$number/$string/"
                    The number is monotonically increasing and unique to every extension.
-                   The string is usually derived from the extensions's name (but shortened, kebab-cased and URL friendly).
+                   The string is usually derived from the extension name (but shortened, kebab-cased and URL friendly).
                    It may diverge from the actual name.
             The keys of "shell_version_map" are GNOME Shell version numbers.
 
@@ -181,7 +195,7 @@ def process_extension(extension: Dict[str, Any]) -> Optional[Dict[str, Any]]:
 
     for shell_version in shell_version_map.keys():
         if pname in package_name_registry[shell_version]:
-            logging.warning(f"Package name '{pname}' is colliding.")
+            logging.warning(f"Package name '{pname}' for GNOME '{shell_version}' is colliding.")
             package_name_registry[shell_version][pname].append(uuid)
         else:
             package_name_registry[shell_version][pname] = [uuid]
@@ -210,18 +224,18 @@ def scrape_extensions_index() -> List[Dict[str, Any]]:
         logging.info("Scraping page " + str(page))
         try:
             with urllib.request.urlopen(
-                f"https://extensions.gnome.org/extension-query/?n_per_page=25&page={page}"
+                    f"https://extensions.gnome.org/extension-query/?n_per_page=25&page={page}"
             ) as response:
                 data = json.loads(response.read().decode())["extensions"]
-                responseLength = len(data)
+                response_length = len(data)
 
                 for extension in data:
                     extensions.append(extension)
 
                 # If our page isn't "full", it must have been the last one
-                if responseLength < 25:
+                if response_length < 25:
                     logging.debug(
-                        f"\tThis page only has {responseLength} entries, so it must be the last one."
+                        f"\tThis page only has {response_length} entries, so it must be the last one."
                     )
                     break
         except urllib.error.HTTPError as e:
@@ -250,11 +264,7 @@ if __name__ == "__main__":
             processed_extensions.append(processed_extension)
             logging.debug(f"Processed {num + 1} / {len(raw_extensions)}")
 
-    logging.info(
-        f"Done. Writing results to extensions.json ({len(processed_extensions)} extensions in total)"
-    )
-
-    with open("extensions.json", "w") as out:
+    with open(updater_dir_path / "extensions.json", "w") as out:
         # Manually pretty-print the outer level, but then do one compact line per extension
         # This allows for the diffs to be manageable (one line of change per extension) despite their quantity
         for index, extension in enumerate(processed_extensions):
@@ -266,14 +276,15 @@ if __name__ == "__main__":
             out.write("\n")
         out.write("]\n")
 
-    with open("extensions.json", "r") as out:
+    logging.info(
+        f"Done. Writing results to extensions.json ({len(processed_extensions)} extensions in total)"
+    )
+
+    with open(updater_dir_path / "extensions.json", "r") as out:
         # Check that the generated file actually is valid JSON, just to be sure
         json.load(out)
 
-    logging.info(
-        "Done. Writing name collisions to collisions.json (please check manually)"
-    )
-    with open("collisions.json", "w") as out:
+    with open(updater_dir_path / "collisions.json", "w") as out:
         # Filter out those that are not duplicates
         package_name_registry_filtered: Dict[ShellVersion, Dict[PackageName, List[Uuid]]] = {
             # The outer level keys are shell versions
@@ -284,3 +295,7 @@ if __name__ == "__main__":
         }
         json.dump(package_name_registry_filtered, out, indent=2, ensure_ascii=False)
         out.write("\n")
+
+    logging.info(
+        "Done. Writing name collisions to collisions.json (please check manually)"
+    )