Forráskód Böngészése

scripts/g.extension.all: fix reinstall multi-addons (#2082)

When reinstalled addon is multi-addon e.g. wx.metadata which contains
multiple individual addons e.g. g.gui.cswbrowser and etc. for every single
addons is returned wx.metadata multi-addon name, to avoid install individual
addons which isn't possible and causes an error.

For finding correct muli-addon name is using modules.xml file, which
have to exists on the osgeo server for download.
Tomas Zigo 3 éve
szülő
commit
cf067d21c8
1 módosított fájl, 133 hozzáadás és 1 törlés
  1. 133 1
      scripts/g.extension.all/g.extension.all.py

+ 133 - 1
scripts/g.extension.all/g.extension.all.py

@@ -36,7 +36,9 @@
 # % label: Force operation (required for removal)
 # % end
 from __future__ import print_function
+import http
 import os
+import re
 import sys
 
 try:
@@ -44,9 +46,17 @@ try:
 except ImportError:
     import elementtree.ElementTree as etree  # Python <= 2.4
 
+from six.moves.urllib import request as urlrequest
+from six.moves.urllib.error import HTTPError, URLError
+
 import grass.script as gscript
 from grass.exceptions import CalledModuleError
 
+HEADERS = {
+    "User-Agent": "Mozilla/5.0",
+}
+HTTP_STATUS_CODES = list(http.HTTPStatus)
+
 
 def get_extensions():
     addon_base = os.getenv("GRASS_ADDON_BASE")
@@ -77,6 +87,128 @@ def get_extensions():
     return ret
 
 
+def urlopen(url, *args, **kwargs):
+    """Wrapper around urlopen. Same function as 'urlopen', but with the
+    ability to define headers.
+    """
+    request = urlrequest.Request(url, headers=HEADERS)
+    return urlrequest.urlopen(request, *args, **kwargs)
+
+
+def download_modules_xml_file(url, response_format, *args, **kwargs):
+    """Generates JSON file containing the download URLs of the official
+    Addons
+
+    :param str url: url address
+    :param str response_format: content type
+
+    :return response: urllib.request.urlopen response object or None
+    """
+    try:
+        response = urlopen(url, *args, **kwargs)
+
+        if not response.code == 200:
+            index = HTTP_STATUS_CODES.index(response.code)
+            desc = HTTP_STATUS_CODES[index].description
+            gscript.fatal(
+                _(
+                    "Download file from <{url}>, "
+                    "return status code {code}, "
+                    "{desc}".format(
+                        url=url,
+                        code=response.code,
+                        desc=desc,
+                    ),
+                ),
+            )
+        if response_format not in response.getheader("Content-Type"):
+            gscript.fatal(
+                _(
+                    "Wrong file format downloaded. "
+                    "Check url <{url}>. Allowed file format is "
+                    "{response_format}.".format(
+                        url=url,
+                        response_format=response_format,
+                    ),
+                ),
+            )
+        return response
+
+    except HTTPError as err:
+        if err.code == 404:
+            gscript.fatal(
+                _(
+                    "The download of the modules.xml file "
+                    "from the server was not successful. "
+                    "File on the server <{url}> doesn't "
+                    "exists.".format(url=url),
+                ),
+            )
+        else:
+            return download_modules_xml_file(
+                url=url,
+                response_format=response_format,
+            )
+    except URLError:
+        gscript.fatal(
+            _(
+                "Download file from <{url}>, "
+                "failed. Check internet connection.".format(
+                    url=url,
+                ),
+            ),
+        )
+
+
+def find_addon_name(addons):
+    """Find correct addon name if addon is a multi-addon
+    e.g. wx.metadata contains multiple modules g.gui.cswbrowser etc.
+
+    Examples:
+    - for the g.gui.cswbrowser module the wx.metadata addon name is
+    returned
+    - for the i.sentinel.download module the i.sentinel addon name is
+    returned
+    etc.
+
+    :param list addons: list of individual addon modules to be reinstalled
+
+    :return set result: set of unique addon names to be reinstalled
+    """
+    grass_version = os.getenv("GRASS_VERSION", "unknown")
+    if grass_version != "unknown":
+        major, minor, patch = grass_version.split(".")
+    else:
+        gscript.fatal(_("Unable to get GRASS GIS version."))
+    url = "https://grass.osgeo.org/addons/grass{major}/modules.xml".format(
+        major=major,
+    )
+    response = download_modules_xml_file(
+        url=url,
+        response_format="application/xml",
+    )
+    tree = etree.fromstring(response.read())
+    result = []
+    for addon in addons:
+        found = False
+        addon_pattern = re.compile(r".*{}$".format(addon))
+        for i in tree:
+            for f in i.findall(".//binary/file"):
+                if re.match(addon_pattern, f.text):
+                    result.append(i.attrib["name"])
+                    found = True
+                    break
+        if not found:
+            gscript.warning(
+                _(
+                    "The <{}> addon cannot be reinstalled. "
+                    "Addon wasn't found among the official "
+                    "addons.".format(addon)
+                ),
+            )
+    return set(result)
+
+
 def main():
     remove = options["operation"] == "remove"
     if remove or flags["f"]:
@@ -103,7 +235,7 @@ def main():
         )
         return 0
 
-    for ext in extensions:
+    for ext in find_addon_name(addons=extensions):
         gscript.message("-" * 60)
         if remove:
             gscript.message(_("Removing extension <%s>...") % ext)