From b15bc1b5bb1926c0c3262952ded1224678e3cbf8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 28 May 2024 19:53:40 +1000 Subject: [PATCH] Extensions: add the blender version to the remote URL Add query arguments to the URL when connecting to the remote server. This information is stripped to create relative paths and for error messages to prevent overly verbose URS's. Based on !122234. Co-authored-by: Dalai Felinto --- .../addons_core/bl_pkg/bl_extension_notify.py | 2 +- .../addons_core/bl_pkg/bl_extension_ops.py | 14 ++++-- .../addons_core/bl_pkg/bl_extension_utils.py | 47 +++++++++++++++++++ scripts/addons_core/bl_pkg/cli/blender_ext.py | 39 +++++++++++---- 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/scripts/addons_core/bl_pkg/bl_extension_notify.py b/scripts/addons_core/bl_pkg/bl_extension_notify.py index 5e58299b8b6..ea7578af0cc 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_notify.py +++ b/scripts/addons_core/bl_pkg/bl_extension_notify.py @@ -168,7 +168,7 @@ def sync_status_generator(repos_notify): cmd_batch_partial.append(partial( bl_extension_utils.repo_sync, directory=repo_item.directory, - remote_url=repo_item.remote_url, + remote_url=bl_extension_ops.url_params_append_defaults(repo_item.remote_url), online_user_agent=bl_extension_ops.online_user_agent_from_blender(), access_token=repo_item.access_token if repo_item.use_access_token else "", # Never sleep while there is no input, as this blocks Blender. diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index f8388bc93f9..4a2c070de58 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -67,6 +67,10 @@ rna_prop_enable_on_install_type_map = { } +def url_params_append_defaults(url): + return bl_extension_utils.url_params_append_for_blender(url, blender_version=bpy.app.version) + + def rna_prop_repo_enum_local_only_itemf(_self, context): if context is None: result = [] @@ -1003,7 +1007,7 @@ class BlPkgRepoSync(Operator, _BlPkgCmdMixIn): partial( bl_extension_utils.repo_sync, directory=directory, - remote_url=repo_item.remote_url, + remote_url=url_params_append_defaults(repo_item.remote_url), online_user_agent=online_user_agent_from_blender(), access_token=repo_item.access_token, use_idle=is_modal, @@ -1080,7 +1084,7 @@ class BlPkgRepoSyncAll(Operator, _BlPkgCmdMixIn): cmd_batch.append(partial( bl_extension_utils.repo_sync, directory=repo_item.directory, - remote_url=repo_item.remote_url, + remote_url=url_params_append_defaults(repo_item.remote_url), online_user_agent=online_user_agent_from_blender(), access_token=repo_item.access_token, use_idle=is_modal, @@ -1208,7 +1212,7 @@ class BlPkgPkgUpgradeAll(Operator, _BlPkgCmdMixIn): cmd_batch.append(partial( bl_extension_utils.pkg_install, directory=repo_item.directory, - remote_url=repo_item.remote_url, + remote_url=url_params_append_defaults(repo_item.remote_url), pkg_id_sequence=pkg_id_sequence, online_user_agent=online_user_agent_from_blender(), access_token=repo_item.access_token, @@ -1308,7 +1312,7 @@ class BlPkgPkgInstallMarked(Operator, _BlPkgCmdMixIn): cmd_batch.append(partial( bl_extension_utils.pkg_install, directory=repo_item.directory, - remote_url=repo_item.remote_url, + remote_url=url_params_append_defaults(repo_item.remote_url), pkg_id_sequence=pkg_id_sequence, online_user_agent=online_user_agent_from_blender(), access_token=repo_item.access_token, @@ -1826,7 +1830,7 @@ class BlPkgPkgInstall(Operator, _BlPkgCmdMixIn): partial( bl_extension_utils.pkg_install, directory=directory, - remote_url=repo_item.remote_url, + remote_url=url_params_append_defaults(repo_item.remote_url), pkg_id_sequence=(pkg_id,), online_user_agent=online_user_agent_from_blender(), access_token=repo_item.access_token, diff --git a/scripts/addons_core/bl_pkg/bl_extension_utils.py b/scripts/addons_core/bl_pkg/bl_extension_utils.py index 461fd6af382..1fc75c3f8a4 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_utils.py +++ b/scripts/addons_core/bl_pkg/bl_extension_utils.py @@ -25,6 +25,7 @@ __all__ = ( # Public Stand-Alone Utilities. "pkg_theme_file_list", + "url_params_append_for_blender", "file_mtime_or_none", # Public API. @@ -277,6 +278,52 @@ def pkg_theme_file_list(directory: str, pkg_idname: str) -> Tuple[str, List[str] return theme_dir, theme_files +def _url_params_append(url: str, params: Dict[str, str]) -> str: + import urllib + import urllib.parse + + # Remove empty parameters. + params = {key: value for key, value in params.items() if value is not None and value != ""} + if not params: + return url + + # Parse the URL to get its scheme, domain, and query parameters. + parsed_url = urllib.parse.urlparse(url) + + # Combine existing query parameters with new parameters + existing_params = urllib.parse.parse_qsl(parsed_url.query) + all_params = dict(existing_params) + all_params.update(params) + + # Encode all parameters into a new query string + new_query = urllib.parse.urlencode(all_params) + + # Combine the scheme, netloc, path, and new query string to form the new URL + new_url = urllib.parse.urlunparse(( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + parsed_url.params, + new_query, + parsed_url.fragment, + )) + + return new_url + + +def url_params_append_for_blender(url: str, blender_version: Tuple[int, int, int]) -> str: + # `blender_version` is typically `bpy.app.version`. + + # While this won't cause errors, it's redundant to add this information to file URL's. + if url.startswith("file://"): + return url + + params = { + "blender_version": "{:d}.{:d}.{:d}".format(*blender_version), + } + return _url_params_append(url, params) + + # ----------------------------------------------------------------------------- # Public Repository Actions # diff --git a/scripts/addons_core/bl_pkg/cli/blender_ext.py b/scripts/addons_core/bl_pkg/cli/blender_ext.py index 6928e7cf022..bb02f7dcebb 100755 --- a/scripts/addons_core/bl_pkg/cli/blender_ext.py +++ b/scripts/addons_core/bl_pkg/cli/blender_ext.py @@ -676,6 +676,23 @@ def remote_url_get(url: str) -> str: return url +def remote_url_params_strip(url: str) -> str: + # Parse the URL to get its scheme, domain, and query parameters. + parsed_url = urllib.parse.urlparse(url) + + # Combine the scheme, netloc, path without any other parameters, stripping the URL. + new_url = urllib.parse.urlunparse(( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + None, # `parsed_url.params,` + None, # `parsed_url.query,` + None, # `parsed_url.fragment,` + )) + + return new_url + + # ----------------------------------------------------------------------------- # ZipFile Helpers @@ -1072,18 +1089,19 @@ def url_retrieve_exception_as_message( Provides more user friendly messages when reading from a URL fails. """ # These exceptions may occur when reading from the file-system or a URL. + url_strip = remote_url_params_strip(url) if isinstance(ex, FileNotFoundError): - return "{:s}: file-not-found ({:s}) reading {!r}!".format(prefix, str(ex), url) + return "{:s}: file-not-found ({:s}) reading {!r}!".format(prefix, str(ex), url_strip) if isinstance(ex, TimeoutError): - return "{:s}: timeout ({:s}) reading {!r}!".format(prefix, str(ex), url) + return "{:s}: timeout ({:s}) reading {!r}!".format(prefix, str(ex), url_strip) if isinstance(ex, urllib.error.URLError): if isinstance(ex, urllib.error.HTTPError): if ex.code == 403: - return "{:s}: HTTP error (403) access token may be incorrect, reading {!r}!".format(prefix, url) - return "{:s}: HTTP error ({:s}) reading {!r}!".format(prefix, str(ex), url) - return "{:s}: URL error ({:s}) reading {!r}!".format(prefix, str(ex), url) + return "{:s}: HTTP error (403) access token may be incorrect, reading {!r}!".format(prefix, url_strip) + return "{:s}: HTTP error ({:s}) reading {!r}!".format(prefix, str(ex), url_strip) + return "{:s}: URL error ({:s}) reading {!r}!".format(prefix, str(ex), url_strip) - return "{:s}: unexpected error ({:s}) reading {!r}!".format(prefix, str(ex), url) + return "{:s}: unexpected error ({:s}) reading {!r}!".format(prefix, str(ex), url_strip) def pkg_idname_is_valid_or_error(pkg_idname: str) -> Optional[str]: @@ -2328,6 +2346,9 @@ class subcmd_client: # Ensure a private directory so a local cache can be created. local_cache_dir = repo_local_private_dir_ensure_with_subdir(local_dir=local_dir, subdir="cache") + # Needed so relative paths can be properly calculated. + remote_url_strip = remote_url_params_strip(remote_url) + # TODO: this could be optimized to only lookup known ID's. json_data_pkg_info_map: Dict[str, Dict[str, Any]] = { pkg_info["id"]: pkg_info for pkg_info in pkg_repo_data.data @@ -2376,10 +2397,10 @@ class subcmd_client: # Remote path. if pkg_archive_url.startswith("./"): - if remote_url_has_filename_suffix(remote_url): - filepath_remote_archive = remote_url.rpartition("/")[0] + pkg_archive_url[1:] + if remote_url_has_filename_suffix(remote_url_strip): + filepath_remote_archive = remote_url_strip.rpartition("/")[0] + pkg_archive_url[1:] else: - filepath_remote_archive = remote_url.rstrip("/") + pkg_archive_url[1:] + filepath_remote_archive = remote_url_strip.rstrip("/") + pkg_archive_url[1:] else: filepath_remote_archive = pkg_archive_url