From 3c5d373fc4dabd7a27da58113d05df8990f745dc Mon Sep 17 00:00:00 2001 From: Thomas Dinges Date: Wed, 24 Nov 2021 14:29:16 +0100 Subject: Magic UV: Remove online updater Add-ons should not connect to external services outside of blender.org See new key requirements: https://wiki.blender.org/wiki/Process/Addons --- magic_uv/__init__.py | 4 - magic_uv/preferences.py | 21 --- magic_uv/updater.py | 144 ---------------- magic_uv/utils/__init__.py | 2 - magic_uv/utils/addon_updater.py | 372 ---------------------------------------- 5 files changed, 543 deletions(-) delete mode 100644 magic_uv/updater.py delete mode 100644 magic_uv/utils/addon_updater.py (limited to 'magic_uv') diff --git a/magic_uv/__init__.py b/magic_uv/__init__.py index d0b8ae45..f31ad26b 100644 --- a/magic_uv/__init__.py +++ b/magic_uv/__init__.py @@ -52,7 +52,6 @@ if "bpy" in locals(): importlib.reload(ui) importlib.reload(properites) importlib.reload(preferences) - importlib.reload(updater) else: import bpy from . import common @@ -61,14 +60,11 @@ else: from . import ui from . import properites from . import preferences - from . import updater import bpy def register(): - updater.register_updater(bl_info) - utils.bl_class_registry.BlClassRegistry.register() properites.init_props(bpy.types.Scene) user_prefs = utils.compatibility.get_user_preferences(bpy.context) diff --git a/magic_uv/preferences.py b/magic_uv/preferences.py index 21f70508..7d6ac957 100644 --- a/magic_uv/preferences.py +++ b/magic_uv/preferences.py @@ -59,9 +59,7 @@ from .ui.IMAGE_MT_uvs import ( MUV_MT_UVInspection, ) from .utils.bl_class_registry import BlClassRegistry -from .utils.addon_updater import AddonUpdaterManager from .utils import compatibility as compat -from . import updater def view3d_uvmap_menu_fn(self, context): @@ -169,14 +167,6 @@ def remove_builtin_menu(): bpy.types.VIEW3D_MT_uv_map.remove(view3d_uvmap_menu_fn) -def get_update_candidate_branches(_, __): - manager = AddonUpdaterManager.get_instance() - if not manager.candidate_checked(): - return [] - - return [(name, name, "") for name in manager.get_candidate_branch_names()] - - def set_debug_mode(self, value): self['enable_debug_mode'] = value @@ -301,7 +291,6 @@ class MUV_Preferences(AddonPreferences): items=[ ('INFO', "Information", "Information about this add-on"), ('CONFIG', "Configuration", "Configuration about this add-on"), - ('UPDATE', "Update", "Update this add-on"), ], default='INFO' ) @@ -336,13 +325,6 @@ class MUV_Preferences(AddonPreferences): default=False ) - # for add-on updater - updater_branch_to_update = EnumProperty( - name="branch", - description="Target branch to update add-on", - items=get_update_candidate_branches - ) - def draw(self, _): layout = self.layout @@ -520,6 +502,3 @@ class MUV_Preferences(AddonPreferences): col.prop(self, "uv_bounding_box_cp_size") col.prop(self, "uv_bounding_box_cp_react_size") layout.separator() - - elif self.category == 'UPDATE': - updater.draw_updater_ui(self) diff --git a/magic_uv/updater.py b/magic_uv/updater.py deleted file mode 100644 index 72d85766..00000000 --- a/magic_uv/updater.py +++ /dev/null @@ -1,144 +0,0 @@ -# - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti " -__status__ = "production" -__version__ = "6.5" -__date__ = "6 Mar 2021" - -import os - -import bpy -from bpy.props import ( - StringProperty, -) - -from .utils.bl_class_registry import BlClassRegistry -from .utils.addon_updater import ( - AddonUpdaterManager, - AddonUpdaterConfig, - get_separator, -) -from .utils import compatibility as compat - - -@BlClassRegistry() -class MUV_OT_CheckAddonUpdate(bpy.types.Operator): - bl_idname = "uv.muv_check_addon_update" - bl_label = "Check Update" - bl_description = "Check Add-on Update" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, _): - updater = AddonUpdaterManager.get_instance() - updater.check_update_candidate() - - return {'FINISHED'} - - -@BlClassRegistry() -@compat.make_annotations -class MUV_OT_UpdateAddon(bpy.types.Operator): - bl_idname = "uv.muv_update_addon" - bl_label = "Update" - bl_description = "Update Add-on" - bl_options = {'REGISTER', 'UNDO'} - - branch_name = StringProperty( - name="Branch Name", - description="Branch name to update", - default="", - ) - - def execute(self, _): - updater = AddonUpdaterManager.get_instance() - updater.update(self.branch_name) - - return {'FINISHED'} - - -def draw_updater_ui(prefs_obj): - layout = prefs_obj.layout - updater = AddonUpdaterManager.get_instance() - - layout.separator() - - if not updater.candidate_checked(): - col = layout.column() - col.scale_y = 2 - row = col.row() - row.operator(MUV_OT_CheckAddonUpdate.bl_idname, - text="Check 'Magic UV' add-on update", - icon='FILE_REFRESH') - else: - row = layout.row(align=True) - row.scale_y = 2 - col = row.column() - col.operator(MUV_OT_CheckAddonUpdate.bl_idname, - text="Check 'Magic UV' add-on update", - icon='FILE_REFRESH') - col = row.column() - if updater.latest_version() != "": - col.enabled = True - ops = col.operator( - MUV_OT_UpdateAddon.bl_idname, - text="Update to the latest release version (version: {})" - .format(updater.latest_version()), - icon='TRIA_DOWN_BAR') - ops.branch_name = updater.latest_version() - else: - col.enabled = False - col.operator(MUV_OT_UpdateAddon.bl_idname, - text="No updates are available.") - - layout.separator() - layout.label(text="Manual Update:") - row = layout.row(align=True) - row.prop(prefs_obj, "updater_branch_to_update", text="Target") - ops = row.operator( - MUV_OT_UpdateAddon.bl_idname, text="Update", - icon='TRIA_DOWN_BAR') - ops.branch_name = prefs_obj.updater_branch_to_update - - layout.separator() - if updater.has_error(): - box = layout.box() - box.label(text=updater.error(), icon='CANCEL') - elif updater.has_info(): - box = layout.box() - box.label(text=updater.info(), icon='ERROR') - - -def register_updater(bl_info): - config = AddonUpdaterConfig() - config.owner = "nutti" - config.repository = "Magic-UV" - config.current_addon_path = os.path.dirname(os.path.realpath(__file__)) - config.branches = ["master"] - config.addon_directory = \ - config.current_addon_path[ - :config.current_addon_path.rfind(get_separator())] - config.min_release_version = bl_info["version"] - config.default_target_addon_path = "magic_uv" - config.target_addon_path = { - "master": "src{}magic_uv".format(get_separator()), - } - updater = AddonUpdaterManager.get_instance() - updater.init(bl_info, config) diff --git a/magic_uv/utils/__init__.py b/magic_uv/utils/__init__.py index 776c7564..87c6eacb 100644 --- a/magic_uv/utils/__init__.py +++ b/magic_uv/utils/__init__.py @@ -25,12 +25,10 @@ __date__ = "6 Mar 2021" if "bpy" in locals(): import importlib - importlib.reload(addon_updater) importlib.reload(bl_class_registry) importlib.reload(compatibility) importlib.reload(property_class_registry) else: - from . import addon_updater from . import bl_class_registry from . import compatibility from . import property_class_registry diff --git a/magic_uv/utils/addon_updater.py b/magic_uv/utils/addon_updater.py deleted file mode 100644 index 8c1601b8..00000000 --- a/magic_uv/utils/addon_updater.py +++ /dev/null @@ -1,372 +0,0 @@ -# - -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -__author__ = "Nutti " -__status__ = "production" -__version__ = "6.5" -__date__ = "6 Mar 2021" - -from threading import Lock -import urllib -import urllib.request -import ssl -import json -import os -import zipfile -import shutil -import datetime - - -def get_separator(): - if os.name == "nt": - return "\\" - return "/" - - -def _request(url, json_decode=True): - # pylint: disable=W0212 - ssl._create_default_https_context = ssl._create_unverified_context - req = urllib.request.Request(url) - - try: - result = urllib.request.urlopen(req) - except urllib.error.HTTPError as e: - raise RuntimeError("HTTP error ({})".format(str(e.code))) - except urllib.error.URLError as e: - raise RuntimeError("URL error ({})".format(str(e.reason))) - - data = result.read() - result.close() - - if json_decode: - try: - return json.JSONDecoder().decode(data.decode()) - except Exception as e: - raise RuntimeError("API response has invalid JSON format ({})" - .format(str(e))) - - return data.decode() - - -def _download(url, path): - try: - urllib.request.urlretrieve(url, path) - except urllib.error.HTTPError as e: - raise RuntimeError("HTTP error ({})".format(str(e.code))) - except urllib.error.URLError as e: - raise RuntimeError("URL error ({})".format(str(e.reason))) - - -def _make_workspace_path(addon_dir): - return addon_dir + get_separator() + "addon_updater_workspace" - - -def _make_workspace(addon_dir): - dir_path = _make_workspace_path(addon_dir) - os.mkdir(dir_path) - - -def _make_temp_addon_path(addon_dir, url): - filename = url.split("/")[-1] - filepath = _make_workspace_path(addon_dir) + get_separator() + filename - return filepath - - -def _download_addon(addon_dir, url): - filepath = _make_temp_addon_path(addon_dir, url) - _download(url, filepath) - - -def _replace_addon(addon_dir, info, current_addon_path, offset_path=""): - # remove current add-on - if os.path.isfile(current_addon_path): - os.remove(current_addon_path) - elif os.path.isdir(current_addon_path): - shutil.rmtree(current_addon_path) - - # replace to the new add-on - workspace_path = _make_workspace_path(addon_dir) - tmp_addon_path = _make_temp_addon_path(addon_dir, info.url) - _, ext = os.path.splitext(tmp_addon_path) - if ext == ".zip": - with zipfile.ZipFile(tmp_addon_path) as zf: - zf.extractall(workspace_path) - if offset_path != "": - src = workspace_path + get_separator() + offset_path - dst = addon_dir - shutil.move(src, dst) - elif ext == ".py": - shutil.move(tmp_addon_path, addon_dir) - else: - raise RuntimeError("Unsupported file extension. (ext: {})".format(ext)) - - -def _get_all_releases_data(owner, repository): - url = "https://api.github.com/repos/{}/{}/releases"\ - .format(owner, repository) - data = _request(url) - - return data - - -def _get_all_branches_data(owner, repository): - url = "https://api.github.com/repos/{}/{}/branches"\ - .format(owner, repository) - data = _request(url) - - return data - - -def _parse_release_version(version): - return [int(c) for c in version[1:].split(".")] - - -# ver1 > ver2 : > 0 -# ver1 == ver2 : == 0 -# ver1 < ver2 : < 0 -def _compare_version(ver1, ver2): - if len(ver1) < len(ver2): - ver1.extend([-1 for _ in range(len(ver2) - len(ver1))]) - elif len(ver1) > len(ver2): - ver2.extend([-1 for _ in range(len(ver1) - len(ver2))]) - - def comp(v1, v2, idx): - if len(v1) == idx: - return 0 # v1 == v2 - - if v1[idx] > v2[idx]: - return 1 # v1 > v2 - if v1[idx] < v2[idx]: - return -1 # v1 < v2 - - return comp(v1, v2, idx + 1) - - return comp(ver1, ver2, 0) - - -class AddonUpdaterConfig: - def __init__(self): - # Name of owner - self.owner = "" - - # Name of repository - self.repository = "" - - # Additional branch for update candidate - self.branches = [] - - # Set minimum release version for update candidate. - # e.g. (5, 2) if your release tag name is "v5.2" - # If you specify (-1, -1), ignore versions less than current add-on - # version specified in bl_info. - self.min_release_version = (-1, -1) - - # Target add-on path - # {"branch/tag": "add-on path"} - self.target_addon_path = {} - - # Default target add-on path. - # Search this path if branch/tag is not found in - # self.target_addon_path. - self.default_target_addon_path = "" - - # Current add-on path - self.current_addon_path = "" - - # Blender add-on directory - self.addon_directory = "" - - -class UpdateCandidateInfo: - def __init__(self): - self.name = "" - self.url = "" - self.group = "" # BRANCH|RELEASE - - -class AddonUpdaterManager: - __inst = None - __lock = Lock() - - __initialized = False - __bl_info = None - __config = None - __update_candidate = [] - __candidate_checked = False - __error = "" - __info = "" - - def __init__(self): - raise NotImplementedError("Not allowed to call constructor") - - @classmethod - def __internal_new(cls): - return super().__new__(cls) - - @classmethod - def get_instance(cls): - if not cls.__inst: - with cls.__lock: - if not cls.__inst: - cls.__inst = cls.__internal_new() - - return cls.__inst - - def init(self, bl_info, config): - self.__bl_info = bl_info - self.__config = config - self.__update_candidate = [] - self.__candidate_checked = False - self.__error = "" - self.__info = "" - self.__initialized = True - - def initialized(self): - return self.__initialized - - def candidate_checked(self): - return self.__candidate_checked - - def check_update_candidate(self): - if not self.initialized(): - raise RuntimeError("AddonUpdaterManager must be initialized") - - self.__update_candidate = [] - self.__candidate_checked = False - - try: - # setup branch information - branches = _get_all_branches_data(self.__config.owner, - self.__config.repository) - for b in branches: - if b["name"] in self.__config.branches: - info = UpdateCandidateInfo() - info.name = b["name"] - info.url = "https://github.com/{}/{}/archive/{}.zip"\ - .format(self.__config.owner, - self.__config.repository, b["name"]) - info.group = 'BRANCH' - self.__update_candidate.append(info) - - # setup release information - releases = _get_all_releases_data(self.__config.owner, - self.__config.repository) - for r in releases: - if _compare_version(_parse_release_version(r["tag_name"]), - self.__config.min_release_version) > 0: - info = UpdateCandidateInfo() - info.name = r["tag_name"] - info.url = r["assets"][0]["browser_download_url"] - info.group = 'RELEASE' - self.__update_candidate.append(info) - except RuntimeError as e: - self.__error = "Failed to check update {}. ({})"\ - .format(str(e), datetime.datetime.now()) - - self.__info = "Checked update. ({})"\ - .format(datetime.datetime.now()) - - self.__candidate_checked = True - - def has_error(self): - return self.__error != "" - - def error(self): - return self.__error - - def has_info(self): - return self.__info != "" - - def info(self): - return self.__info - - def update(self, version_name): - if not self.initialized(): - raise RuntimeError("AddonUpdaterManager must be initialized.") - - if not self.candidate_checked(): - raise RuntimeError("Update candidate is not checked.") - - info = None - for info in self.__update_candidate: - if info.name == version_name: - break - else: - raise RuntimeError("{} is not found in update candidate" - .format(version_name)) - - if info is None: - raise RuntimeError("Not found any update candidates") - - try: - # create workspace - _make_workspace(self.__config.addon_directory) - # download add-on - _download_addon(self.__config.addon_directory, info.url) - - # get add-on path - if info.name in self.__config.target_addon_path: - addon_path = self.__config.target_addon_path[info.name] - else: - addon_path = self.__config.default_target_addon_path - - # replace add-on - offset_path = "" - if info.group == 'BRANCH': - offset_path = "{}-{}{}{}".format( - self.__config.repository, info.name, get_separator(), - addon_path) - elif info.group == 'RELEASE': - offset_path = addon_path - _replace_addon(self.__config.addon_directory, - info, self.__config.current_addon_path, - offset_path) - - self.__info = "Updated to {}. ({})" \ - .format(info.name, datetime.datetime.now()) - except RuntimeError as e: - self.__error = "Failed to update {}. ({})"\ - .format(str(e), datetime.datetime.now()) - - shutil.rmtree(_make_workspace_path(self.__config.addon_directory)) - - def get_candidate_branch_names(self): - if not self.initialized(): - raise RuntimeError("AddonUpdaterManager must be initialized.") - - if not self.candidate_checked(): - raise RuntimeError("Update candidate is not checked.") - - return [info.name for info in self.__update_candidate] - - def latest_version(self): - release_versions = [info.name - for info in self.__update_candidate - if info.group == 'RELEASE'] - - latest = "" - for version in release_versions: - if latest == "": - latest = version - elif _compare_version(_parse_release_version(version), - _parse_release_version(latest)) > 0: - latest = version - - return latest -- cgit v1.2.3