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
This commit is contained in:
Thomas Dinges 2021-11-24 14:29:16 +01:00 committed by Philipp Oeser
parent 0521e90f2d
commit 69c817b87a
Notes: blender-bot 2023-02-13 12:29:35 +01:00
Referenced by issue blender/blender#88449: Blender LTS: Maintenance Task 2.93
Referenced by issue blender/blender#88449, Blender LTS: Maintenance Task 2.93
5 changed files with 0 additions and 543 deletions

View File

@ -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)

View File

@ -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)

View File

@ -1,144 +0,0 @@
# <pep8-80 compliant>
# ##### 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 <nutti.metro@gmail.com>"
__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)

View File

@ -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

View File

@ -1,372 +0,0 @@
# <pep8-80 compliant>
# ##### 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 <nutti.metro@gmail.com>"
__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