diff options
author | bartus | 2019-05-14 21:38:23 +0200 |
---|---|---|
committer | bartus | 2019-05-14 21:38:23 +0200 |
commit | dc69d64178ed68b66c78031ccbd24a2b3a081e8b (patch) | |
tree | cf9db880c92ef80871cf257584690c405a0eb5e4 | |
parent | f87e9bc507363372055bfd1b6beb21e5aa45fd72 (diff) | |
download | aur-dc69d64178ed68b66c78031ccbd24a2b3a081e8b.tar.gz |
update to v3.1.2
-rw-r--r-- | .SRCINFO | 6 | ||||
-rw-r--r-- | PKGBUILD | 8 | ||||
-rw-r--r-- | remove.updater.patch | 3204 |
3 files changed, 3168 insertions, 50 deletions
@@ -1,7 +1,7 @@ pkgbase = blender-plugin-gaffer-git pkgdesc = Blender addon for light and hdri managament. - pkgver = 3.0.4.r1.gf74e188 - pkgrel = 2 + pkgver = 3.1.2.r0.gf895629 + pkgrel = 1 url = https://blendermarket.com/products/gaffer-light-manager/ install = blender-plugin-gaffer-git.install arch = any @@ -11,7 +11,7 @@ pkgbase = blender-plugin-gaffer-git source = gaffer::git+https://github.com/gregzaal/gaffer.git source = remove.updater.patch md5sums = SKIP - md5sums = ea6aa0e22871607e9b8d9285c367ad86 + md5sums = f5605da2a9b5ed7f69eeebdcd057cf69 pkgname = blender-plugin-gaffer-git @@ -5,8 +5,8 @@ git_user_name=gregzaal _blender=$(pacman -Sddp --print-format %v blender|grep -oP '(?<=\:)[[:digit:]]{1}\.[[:digit:]]{2}(?=\.)') pkgname=blender-plugin-${name}-git -pkgver=3.0.4.r1.gf74e188 -pkgrel=2 +pkgver=3.1.2.r0.gf895629 +pkgrel=1 pkgdesc="Blender addon for light and hdri managament." arch=('any') url="https://blendermarket.com/products/gaffer-light-manager/" @@ -17,11 +17,11 @@ install="${pkgname}.install" source=("${name}::git+https://github.com/${git_user_name}/${name}.git" "remove.updater.patch") md5sums=('SKIP' - 'ea6aa0e22871607e9b8d9285c367ad86') + 'f5605da2a9b5ed7f69eeebdcd057cf69') prepare() { cd ${name} - patch -Np1 -i ../remove.updater.patch +# patch -Np1 -i ../remove.updater.patch rm addon_updater.py addon_updater_ops.py } diff --git a/remove.updater.patch b/remove.updater.patch index 88188fdf06e0..a1f6a0a22eec 100644 --- a/remove.updater.patch +++ b/remove.updater.patch @@ -1,75 +1,74 @@ diff --git a/__init__.py b/__init__.py -index 0a39c25..d0ca78a 100644 +index 9781923..eed0f02 100644 --- a/__init__.py +++ b/__init__.py -@@ -34,16 +34,13 @@ if "bpy" in locals(): +@@ -34,8 +34,6 @@ if "bpy" in locals(): imp.reload(functions) imp.reload(operators) imp.reload(ui) - imp.reload(addon_updater) - imp.reload(addon_updater_ops) else: -- from . import constants, functions, operators, ui, addon_updater, addon_updater_ops -+ from . import constants, functions, operators, ui + from . import constants, functions, operators, ui - import bpy - import os +@@ -44,7 +42,6 @@ import os import json - import bgl, blf + import bgl + import blf -from . import addon_updater_ops from collections import OrderedDict from math import pi, cos, sin, log from mathutils import Vector, Matrix -@@ -54,40 +51,6 @@ from bpy.app.handlers import persistent +@@ -55,40 +52,6 @@ from bpy.app.handlers import persistent class GafferPreferences(bpy.types.AddonPreferences): bl_idname = __package__ -- # addon updater preferences -- auto_check_update = bpy.props.BoolProperty( -- name = "Auto-check for Update", -- description = "If enabled, auto-check for updates using an interval", -- default = True, -- ) -- updater_intrval_months = bpy.props.IntProperty( +- # Add-on Updater Prefs +- auto_check_update: bpy.props.BoolProperty( +- name="Auto-check for Update", +- description="If enabled, auto-check for updates using an interval", +- default=True, +- ) +- updater_intrval_months: bpy.props.IntProperty( - name='Months', -- description = "Number of months between checking for updates", +- description="Number of months between checking for updates", - default=0, - min=0 -- ) -- updater_intrval_days = bpy.props.IntProperty( +- ) +- updater_intrval_days: bpy.props.IntProperty( - name='Days', -- description = "Number of days between checking for updates", +- description="Number of days between checking for updates", - default=1, - min=0, -- ) -- updater_intrval_hours = bpy.props.IntProperty( +- ) +- updater_intrval_hours: bpy.props.IntProperty( - name='Hours', -- description = "Number of hours between checking for updates", +- description="Number of hours between checking for updates", - default=0, - min=0, - max=23 -- ) -- updater_intrval_minutes = bpy.props.IntProperty( +- ) +- updater_intrval_minutes: bpy.props.IntProperty( - name='Minutes', -- description = "Number of minutes between checking for updates", +- description="Number of minutes between checking for updates", - default=0, - min=0, - max=59 -- ) -- updater_expand_prefs = bpy.props.BoolProperty(default=False) +- ) +- updater_expand_prefs: bpy.props.BoolProperty(default=False) - - hdri_path = bpy.props.StringProperty( - name="HDRI Folder", - subtype='DIR_PATH', -@@ -166,7 +129,6 @@ class GafferPreferences(bpy.types.AddonPreferences): + # Add-on Prefs + show_hdri_list: bpy.props.BoolProperty( + name="Show", +@@ -184,7 +147,6 @@ class GafferPreferences(bpy.types.AddonPreferences): row.alignment = 'RIGHT' row.prop(self, 'include_8bit') -- addon_updater_ops.update_settings_ui(self,context) +- addon_updater_ops.update_settings_ui(self, context) box = layout.box() col = box.column() -@@ -531,7 +493,6 @@ class GafferProperties(bpy.types.PropertyGroup): +@@ -628,7 +590,6 @@ classes = [ def register(): @@ -77,8 +76,3127 @@ index 0a39c25..d0ca78a 100644 functions.previews_register() functions.cleanup_logs() +diff --git a/addon_updater.py b/addon_updater.py +deleted file mode 100644 +index 372aa33..0000000 +--- a/addon_updater.py ++++ /dev/null +@@ -1,1664 +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 ##### +- +- +-""" +-See documentation for usage +-https://github.com/CGCookie/blender-addon-updater +- +-""" +- +-import errno +-import ssl +-import urllib.request +-import urllib +-import os +-import json +-import zipfile +-import shutil +-import threading +-import fnmatch +-from datetime import datetime, timedelta +- +-# blender imports, used in limited cases +-import bpy +-import addon_utils +- +-# ----------------------------------------------------------------------------- +-# Define error messages/notices & hard coded globals +-# ----------------------------------------------------------------------------- +- +-# currently not used +-DEFAULT_TIMEOUT = 10 +-DEFAULT_PER_PAGE = 30 +- +- +-# ----------------------------------------------------------------------------- +-# The main class +-# ----------------------------------------------------------------------------- +- +-class Singleton_updater(object): +- """ +- This is the singleton class to reference a copy from, +- it is the shared module level class +- """ +- def __init__(self): +- +- self._engine = GithubEngine() +- self._user = None +- self._repo = None +- self._website = None +- self._current_version = None +- self._subfolder_path = None +- self._tags = [] +- self._tag_latest = None +- self._tag_names = [] +- self._latest_release = None +- self._use_releases = False +- self._include_branches = False +- self._include_branch_list = ['master'] +- self._include_branch_autocheck = False +- self._manual_only = False +- self._version_min_update = None +- self._version_max_update = (3, 1, 0) +- +- # by default, backup current addon if new is being loaded +- self._backup_current = True +- self._backup_ignore_patterns = None +- +- # set patterns for what files to overwrite on update +- self._overwrite_patterns = ["*.py","*.pyc"] +- self._remove_pre_update_patterns = [] +- +- # by default, don't auto enable/disable the addon on update +- # as it is slightly less stable/won't always fully reload module +- self._auto_reload_post_update = False +- +- # settings relating to frequency and whether to enable auto background check +- self._check_interval_enable = False +- self._check_interval_months = 0 +- self._check_interval_days = 7 +- self._check_interval_hours = 0 +- self._check_interval_minutes = 0 +- +- # runtime variables, initial conditions +- self._verbose = False +- self._fake_install = False +- self._async_checking = False # only true when async daemon started +- self._update_ready = None +- self._update_link = None +- self._update_version = None +- self._source_zip = None +- self._check_thread = None +- self._select_link = None +- self.skip_tag = None +- +- # get from module data +- self._addon = __package__.lower() +- self._addon_package = __package__ # must not change +- self._updater_path = os.path.join(os.path.dirname(__file__), +- self._addon+"_updater") +- self._addon_root = os.path.dirname(__file__) +- self._json = {} +- self._error = None +- self._error_msg = None +- self._prefiltered_tag_count = 0 +- +- # UI code only, ie not used within this module but still useful +- # properties to have +- +- # to verify a valid import, in place of placeholder import +- self.showpopups = True # used in UI to show or not show update popups +- self.invalidupdater = False +- +- # pre-assign basic select-link function +- def select_link_function(self, tag): +- return tag["zipball_url"] +- +- self._select_link = select_link_function +- +- +- # ------------------------------------------------------------------------- +- # Getters and setters +- # ------------------------------------------------------------------------- +- +- +- @property +- def addon(self): +- return self._addon +- @addon.setter +- def addon(self, value): +- self._addon = str(value) +- +- @property +- def api_url(self): +- return self._engine.api_url +- @api_url.setter +- def api_url(self, value): +- if self.check_is_url(value) == False: +- raise ValueError("Not a valid URL: " + value) +- self._engine.api_url = value +- +- @property +- def async_checking(self): +- return self._async_checking +- +- @property +- def auto_reload_post_update(self): +- return self._auto_reload_post_update +- @auto_reload_post_update.setter +- def auto_reload_post_update(self, value): +- try: +- self._auto_reload_post_update = bool(value) +- except: +- raise ValueError("Must be a boolean value") +- +- @property +- def backup_current(self): +- return self._backup_current +- @backup_current.setter +- def backup_current(self, value): +- if value == None: +- self._backup_current = False +- return +- else: +- self._backup_current = value +- +- @property +- def backup_ignore_patterns(self): +- return self._backup_ignore_patterns +- @backup_ignore_patterns.setter +- def backup_ignore_patterns(self, value): +- if value == None: +- self._backup_ignore_patterns = None +- return +- elif type(value) != type(['list']): +- raise ValueError("Backup pattern must be in list format") +- else: +- self._backup_ignore_patterns = value +- +- @property +- def check_interval(self): +- return (self._check_interval_enable, +- self._check_interval_months, +- self._check_interval_days, +- self._check_interval_hours, +- self._check_interval_minutes) +- +- @property +- def current_version(self): +- return self._current_version +- @current_version.setter +- def current_version(self, tuple_values): +- if tuple_values==None: +- self._current_version = None +- return +- elif type(tuple_values) is not tuple: +- try: +- tuple(tuple_values) +- except: +- raise ValueError( +- "Not a tuple! current_version must be a tuple of integers") +- for i in tuple_values: +- if type(i) is not int: +- raise ValueError( +- "Not an integer! current_version must be a tuple of integers") +- self._current_version = tuple(tuple_values) +- +- @property +- def engine(self): +- return self._engine.name +- @engine.setter +- def engine(self, value): +- if value.lower()=="github": +- self._engine = GithubEngine() +- elif value.lower()=="gitlab": +- self._engine = GitlabEngine() +- elif value.lower()=="bitbucket": +- self._engine = BitbucketEngine() +- else: +- raise ValueError("Invalid engine selection") +- +- @property +- def error(self): +- return self._error +- +- @property +- def error_msg(self): +- return self._error_msg +- +- @property +- def fake_install(self): +- return self._fake_install +- @fake_install.setter +- def fake_install(self, value): +- if type(value) != type(False): +- raise ValueError("fake_install must be a boolean value") +- self._fake_install = bool(value) +- +- # not currently used +- @property +- def include_branch_autocheck(self): +- return self._include_branch_autocheck +- @include_branch_autocheck.setter +- def include_branch_autocheck(self, value): +- try: +- self._include_branch_autocheck = bool(value) +- except: +- raise ValueError("include_branch_autocheck must be a boolean value") +- +- @property +- def include_branch_list(self): +- return self._include_branch_list +- @include_branch_list.setter +- def include_branch_list(self, value): +- try: +- if value == None: +- self._include_branch_list = ['master'] +- elif type(value) != type(['master']) or value==[]: +- raise ValueError("include_branch_list should be a list of valid branches") +- else: +- self._include_branch_list = value +- except: +- raise ValueError("include_branch_list should be a list of valid branches") +- +- @property +- def include_branches(self): +- return self._include_branches +- @include_branches.setter +- def include_branches(self, value): +- try: +- self._include_branches = bool(value) +- except: +- raise ValueError("include_branches must be a boolean value") +- +- @property +- def json(self): +- if self._json == {}: +- self.set_updater_json() +- return self._json +- +- @property +- def latest_release(self): +- if self._latest_release == None: +- return None +- return self._latest_release +- +- @property +- def manual_only(self): +- return self._manual_only +- @manual_only.setter +- def manual_only(self, value): +- try: +- self._manual_only = bool(value) +- except: +- raise ValueError("manual_only must be a boolean value") +- +- @property +- def overwrite_patterns(self): +- return self._overwrite_patterns +- @overwrite_patterns.setter +- def overwrite_patterns(self, value): +- if value == None: +- self._overwrite_patterns = ["*.py","*.pyc"] +- elif type(value) != type(['']): +- raise ValueError("overwrite_patterns needs to be in a list format") +- else: +- self._overwrite_patterns = value +- +- @property +- def private_token(self): +- return self._engine.token +- @private_token.setter +- def private_token(self, value): +- if value==None: +- self._engine.token = None +- else: +- self._engine.token = str(value) +- +- @property +- def remove_pre_update_patterns(self): +- return self._remove_pre_update_patterns +- @remove_pre_update_patterns.setter +- def remove_pre_update_patterns(self, value): +- if value == None: +- self._remove_pre_update_patterns = [] +- elif type(value) != type(['']): +- raise ValueError("remove_pre_update_patterns needs to be in a list format") +- else: +- self._remove_pre_update_patterns = value +- +- @property +- def repo(self): +- return self._repo +- @repo.setter +- def repo(self, value): +- try: +- self._repo = str(value) +- except: +- raise ValueError("User must be a string") +- +- @property +- def select_link(self): +- return self._select_link +- @select_link.setter +- def select_link(self, value): +- # ensure it is a function assignment, with signature: +- # input self, tag; returns link name +- if not hasattr(value, "__call__"): +- raise ValueError("select_link must be a function") +- self._select_link = value +- +- @property +- def stage_path(self): +- return self._updater_path +- @stage_path.setter +- def stage_path(self, value): +- if value == None: +- if self._verbose: print("Aborting assigning stage_path, it's null") +- return +- elif value != None and not os.path.exists(value): +- try: +- os.makedirs(value) +- except: +- if self._verbose: print("Error trying to staging path") +- return +- self._updater_path = value +- +- @property +- def subfolder_path(self): +- return self._subfolder_path +- @subfolder_path.setter +- def subfolder_path(self, value): +- self._subfolder_path = value +- +- @property +- def tags(self): +- if self._tags == []: +- return [] +- tag_names = [] +- for tag in self._tags: +- tag_names.append(tag["name"]) +- return tag_names +- +- @property +- def tag_latest(self): +- if self._tag_latest == None: +- return None +- return self._tag_latest["name"] +- +- @property +- def update_link(self): +- return self._update_link +- +- @property +- def update_ready(self): +- return self._update_ready +- +- @property +- def update_version(self): +- return self._update_version +- +- @property +- def use_releases(self): +- return self._use_releases +- @use_releases.setter +- def use_releases(self, value): +- try: +- self._use_releases = bool(value) +- except: +- raise ValueError("use_releases must be a boolean value") +- +- @property +- def user(self): +- return self._user +- @user.setter +- def user(self, value): +- try: +- self._user = str(value) +- except: +- raise ValueError("User must be a string value") +- +- @property +- def verbose(self): +- return self._verbose +- @verbose.setter +- def verbose(self, value): +- try: +- self._verbose = bool(value) +- if self._verbose == True: +- print(self._addon+" updater verbose is enabled") +- except: +- raise ValueError("Verbose must be a boolean value") +- +- @property +- def version_max_update(self): +- return self._version_max_update +- @version_max_update.setter +- def version_max_update(self, value): +- if value == None: +- self._version_max_update = None +- return +- if type(value) != type((1,2,3)): +- raise ValueError("Version maximum must be a tuple") +- for subvalue in value: +- if type(subvalue) != int: +- raise ValueError("Version elements must be integers") +- self._version_max_update = value +- +- @property +- def version_min_update(self): +- return self._version_min_update +- @version_min_update.setter +- def version_min_update(self, value): +- if value == None: +- self._version_min_update = None +- return +- if type(value) != type((1,2,3)): +- raise ValueError("Version minimum must be a tuple") +- for subvalue in value: +- if type(subvalue) != int: +- raise ValueError("Version elements must be integers") +- self._version_min_update = value +- +- @property +- def website(self): +- return self._website +- @website.setter +- def website(self, value): +- if self.check_is_url(value) == False: +- raise ValueError("Not a valid URL: " + value) +- self._website = value +- +- +- # ------------------------------------------------------------------------- +- # Parameter validation related functions +- # ------------------------------------------------------------------------- +- +- +- def check_is_url(self, url): +- if not ("http://" in url or "https://" in url): +- return False +- if "." not in url: +- return False +- return True +- +- def get_tag_names(self): +- tag_names = [] +- self.get_tags() +- for tag in self._tags: +- tag_names.append(tag["name"]) +- return tag_names +- +- def set_check_interval(self,enable=False,months=0,days=14,hours=0,minutes=0): +- # enabled = False, default initially will not check against frequency +- # if enabled, default is then 2 weeks +- +- if type(enable) is not bool: +- raise ValueError("Enable must be a boolean value") +- if type(months) is not int: +- raise ValueError("Months must be an integer value") +- if type(days) is not int: +- raise ValueError("Days must be an integer value") +- if type(hours) is not int: +- raise ValueError("Hours must be an integer value") +- if type(minutes) is not int: +- raise ValueError("Minutes must be an integer value") +- +- if enable==False: +- self._check_interval_enable = False +- else: +- self._check_interval_enable = True +- +- self._check_interval_months = months +- self._check_interval_days = days +- self._check_interval_hours = hours +- self._check_interval_minutes = minutes +- +- # declare how the class gets printed +- +- def __repr__(self): +- return "<Module updater from {a}>".format(a=__file__) +- +- def __str__(self): +- return "Updater, with user: {a}, repository: {b}, url: {c}".format( +- a=self._user, +- b=self._repo, c=self.form_repo_url()) +- +- +- # ------------------------------------------------------------------------- +- # API-related functions +- # ------------------------------------------------------------------------- +- +- def form_repo_url(self): +- return self._engine.form_repo_url(self) +- +- def form_tags_url(self): +- return self._engine.form_tags_url(self) +- +- def form_branch_url(self, branch): +- return self._engine.form_branch_url(branch, self) +- +- def get_tags(self): +- request = self.form_tags_url() +- if self._verbose: print("Getting tags from server") +- +- # get all tags, internet call +- all_tags = self._engine.parse_tags(self.get_api(request), self) +- if all_tags is not None: +- self._prefiltered_tag_count = len(all_tags) +- else: +- self._prefiltered_tag_count = 0 +- all_tags = [] +- +- # pre-process to skip tags +- if self.skip_tag != None: +- self._tags = [tg for tg in all_tags if self.skip_tag(self, tg)==False] +- else: +- self._tags = all_tags +- +- # get additional branches too, if needed, and place in front +- # Does NO checking here whether branch is valid +- if self._include_branches == True: +- temp_branches = self._include_branch_list.copy() +- temp_branches.reverse() +- for branch in temp_branches: +- request = self.form_branch_url(branch) +- include = { +- "name":branch.title(), +- "zipball_url":request +- } +- self._tags = [include] + self._tags # append to front +- +- if self._tags == None: +- # some error occurred +- self._tag_latest = None +- self._tags = [] +- return +- elif self._prefiltered_tag_count == 0 and self._include_branches == False: +- self._tag_latest = None +- if self._error == None: # if not None, could have had no internet +- self._error = "No releases found" +- self._error_msg = "No releases or tags found on this repository" +- if self._verbose: print("No releases or tags found on this repository") +- elif self._prefiltered_tag_count == 0 and self._include_branches == True: +- if not self._error: self._tag_latest = self._tags[0] +- if self._verbose: +- branch = self._include_branch_list[0] +- print("{} branch found, no releases".format(branch), self._tags[0]) +- elif (len(self._tags)-len(self._include_branch_list)==0 and self._include_branches==True) \ +- or (len(self._tags)==0 and self._include_branches==False) \ +- and self._prefiltered_tag_count > 0: +- self._tag_latest = None +- self._error = "No releases available" +- self._error_msg = "No versions found within compatible version range" +- if self._verbose: print("No versions found within compatible version range") +- else: +- if self._include_branches == False: +- self._tag_latest = self._tags[0] +- if self._verbose: print("Most recent tag found:",self._tags[0]['name']) +- else: +- # don't return branch if in list +- n = len(self._include_branch_list) +- self._tag_latest = self._tags[n] # guaranteed at least len()=n+1 +- if self._verbose: print("Most recent tag found:",self._tags[n]['name']) +- +- +- # all API calls to base url +- def get_raw(self, url): +- # print("Raw request:", url) +- request = urllib.request.Request(url) +- try: +- context = ssl._create_unverified_context() +- except: +- # some blender packaged python versions don't have this, largely +- # useful for local network setups otherwise minimal impact +- context = None +- +- # setup private request headers if appropriate +- if self._engine.token != None: +- if self._engine.name == "gitlab": +- request.add_header('PRIVATE-TOKEN',self._engine.token) +- else: +- if self._verbose: print("Tokens not setup for engine yet") +- +- # run the request +- try: +- if context: +- result = urllib.request.urlopen(request, context=context) +- else: +- result = urllib.request.urlopen(request) +- except urllib.error.HTTPError as e: +- if str(e.code) == "403": +- self._error = "HTTP error (access denied)" +- self._error_msg = str(e.code) + " - server error response" +- print(self._error, self._error_msg) +- else: +- self._error = "HTTP error" +- self._error_msg = str(e.code) +- print(self._error, self._error_msg) +- self._update_ready = None +- except urllib.error.URLError as e: +- reason = str(e.reason) +- if "TLSV1_ALERT" in reason or "SSL" in reason.upper(): +- self._error = "Connection rejected, download manually" +- self._error_msg = reason +- print(self._error, self._error_msg) +- else: +- self._error = "URL error, check internet connection" +- self._error_msg = reason +- print(self._error, self._error_msg) +- self._update_ready = None +- return None +- else: +- result_string = result.read() +- result.close() +- return result_string.decode() +- +- +- # result of all api calls, decoded into json format +- def get_api(self, url): +- # return the json version +- get = None +- get = self.get_raw(url) +- if get != None: +- try: +- return json.JSONDecoder().decode(get) +- except Exception as e: +- self._error = "API response has invalid JSON format" +- self._error_msg = str(e.reason) +- self._update_ready = None +- print(self._error, self._error_msg) +- return None +- else: +- return None +- +- +- # create a working directory and download the new files +- def stage_repository(self, url): +- +- local = os.path.join(self._updater_path,"update_staging") +- error = None +- +- # make/clear the staging folder +- # ensure the folder is always "clean" +- if self._verbose: print("Preparing staging folder for download:\n",local) +- if os.path.isdir(local) == True: +- try: +- shutil.rmtree(local) +- os.makedirs(local) +- except: +- error = "failed to remove existing staging directory" +- else: +- try: +- os.makedirs(local) +- except: +- error = "failed to create staging directory" +- +- if error != None: +- if self._verbose: print("Error: Aborting update, "+error) +- self._error = "Update aborted, staging path error" +- self._error_msg = "Error: {}".format(error) +- return False +- +- if self._backup_current==True: +- self.create_backup() +- if self._verbose: print("Now retrieving the new source zip") +- +- self._source_zip = os.path.join(local,"source.zip") +- +- if self._verbose: print("Starting download update zip") +- try: +- request = urllib.request.Request(url) +- context = ssl._create_unverified_context() +- +- # setup private token if appropriate +- if self._engine.token != None: +- if self._engine.name == "gitlab": +- request.add_header('PRIVATE-TOKEN',self._engine.token) +- else: +- if self._verbose: print("Tokens not setup for selected engine yet") +- self.urlretrieve(urllib.request.urlopen(request,context=context), self._source_zip) +- # add additional checks on file size being non-zero +- if self._verbose: print("Successfully downloaded update zip") +- return True +- except Exception as e: +- self._error = "Error retrieving download, bad link?" +- self._error_msg = "Error: {}".format(e) +- if self._verbose: +- print("Error retrieving download, bad link?") +- print("Error: {}".format(e)) +- return False +- +- +- def create_backup(self): +- if self._verbose: print("Backing up current addon folder") +- local = os.path.join(self._updater_path,"backup") +- tempdest = os.path.join(self._addon_root, +- os.pardir, +- self._addon+"_updater_backup_temp") +- +- if self._verbose: print("Backup destination path: ",local) +- +- if os.path.isdir(local): +- try: +- shutil.rmtree(local) +- except: +- if self._verbose:print("Failed to removed previous backup folder, contininuing") +- +- # remove the temp folder; shouldn't exist but could if previously interrupted +- if os.path.isdir(tempdest): +- try: +- shutil.rmtree(tempdest) +- except: +- if self._verbose:print("Failed to remove existing temp folder, contininuing") +- # make the full addon copy, which temporarily places outside the addon folder +- if self._backup_ignore_patterns != None: +- shutil.copytree( +- self._addon_root,tempdest, +- ignore=shutil.ignore_patterns(*self._backup_ignore_patterns)) +- else: +- shutil.copytree(self._addon_root,tempdest) +- shutil.move(tempdest,local) +- +- # save the date for future ref +- now = datetime.now() +- self._json["backup_date"] = "{m}-{d}-{yr}".format( +- m=now.strftime("%B"),d=now.day,yr=now.year) +- self.save_updater_json() +- +- def restore_backup(self): +- if self._verbose: print("Restoring backup") +- +- if self._verbose: print("Backing up current addon folder") +- backuploc = os.path.join(self._updater_path,"backup") +- tempdest = os.path.join(self._addon_root, +- os.pardir, +- self._addon+"_updater_backup_temp") +- tempdest = os.path.abspath(tempdest) +- +- # make the copy +- shutil.move(backuploc,tempdest) +- shutil.rmtree(self._addon_root) +- os.rename(tempdest,self._addon_root) +- +- self._json["backup_date"] = "" +- self._json["just_restored"] = True +- self._json["just_updated"] = True +- self.save_updater_json() +- +- self.reload_addon() +- +- def unpack_staged_zip(self,clean=False): +- """Unzip the downloaded file, and validate contents""" +- if os.path.isfile(self._source_zip) == False: +- if self._verbose: print("Error, update zip not found") +- self._error = "Install failed" +- self._error_msg = "Downloaded zip not found" +- return -1 +- +- # clear the existing source folder in case previous files remain +- outdir = os.path.join(self._updater_path, "source") +- try: +- shutil.rmtree(outdir) +- os.makedirs(outdir) +- if self._verbose: +- print("Source folder cleared and recreated") +- except: +- pass +- +- # Create parent directories if needed, would not be relevant unless +- # installing addon into another location or via an addon manager +- try: +- os.mkdir(outdir) +- except Exception as err: +- print("Error occurred while making extract dir:") +- print(str(err)) +- self._error = "Install failed" +- self._error_msg = "Failed to make extract directory" +- return -1 +- +- if not os.path.isdir(outdir): +- print("Failed to create source directory") +- self._error = "Install failed" +- self._error_msg = "Failed to create extract directory" +- return -1 +- +- if self._verbose: +- print("Begin extracting source from zip:", self._source_zip) +- zfile = zipfile.ZipFile(self._source_zip, "r") +- +- if not zfile: +- if self._verbose: +- print("Resulting file is not a zip, cannot extract") +- self._error = "Install failed" +- self._error_msg = "Resulting file is not a zip, cannot extract" +- return -1 +- +- # Now extract directly from the first subfolder (not root) +- # this avoids adding the first subfolder to the path length, +- # which can be too long if the download has the SHA in the name +- zsep = '/' #os.sep # might just always be / even on windows +- for name in zfile.namelist(): +- if zsep not in name: +- continue +- top_folder = name[:name.index(zsep)+1] +- if name == top_folder + zsep: +- continue # skip top level folder +- subpath = name[name.index(zsep)+1:] +- if name.endswith(zsep): +- try: +- os.mkdir(os.path.join(outdir, subpath)) +- if self._verbose: +- print("Extract - mkdir: ", os.path.join(outdir, subpath)) +- except OSError as exc: +- if exc.errno != errno.EEXIST: +- self._error = "Install failed" +- self._error_msg = "Could not create folder from zip" +- return -1 +- else: +- with open(os.path.join(outdir, subpath), "wb") as outfile: +- data = zfile.read(name) +- outfile.write(data) +- if self._verbose: +- print("Extract - create:", os.path.join(outdir, subpath)) +- +- if self._verbose: +- print("Extracted source") +- +- unpath = os.path.join(self._updater_path, "source") +- if not os.path.isdir(unpath): +- self._error = "Install failed" +- self._error_msg = "Extracted path does not exist" +- print("Extracted path does not exist: ", unpath) +- return -1 +- +- if self._subfolder_path: +- self._subfolder_path.replace('/', os.path.sep) +- self._subfolder_path.replace('\\', os.path.sep) +- +- # either directly in root of zip/one subfolder, or use specified path +- if os.path.isfile(os.path.join(unpath,"__init__.py")) == False: +- dirlist = os.listdir(unpath) +- if len(dirlist)>0: +- if self._subfolder_path == "" or self._subfolder_path == None: +- unpath = os.path.join(unpath, dirlist[0]) +- else: +- unpath = os.path.join(unpath, self._subfolder_path) +- +- # smarter check for additional sub folders for a single folder +- # containing __init__.py +- if os.path.isfile(os.path.join(unpath,"__init__.py")) == False: +- if self._verbose: +- print("not a valid addon found") +- print("Paths:") +- print(dirlist) +- self._error = "Install failed" +- self._error_msg = "No __init__ file found in new source" +- return -1 +- +- # merge code with running addon directory, using blender default behavior +- # plus any modifiers indicated by user (e.g. force remove/keep) +- self.deepMergeDirectory(self._addon_root, unpath, clean) +- +- # Now save the json state +- # Change to True, to trigger the handler on other side +- # if allowing reloading within same blender instance +- self._json["just_updated"] = True +- self.save_updater_json() +- self.reload_addon() +- self._update_ready = False +- return 0 +- +- +- def deepMergeDirectory(self,base,merger,clean=False): +- """Merge folder 'merger' into folder 'base' without deleting existing""" +- if not os.path.exists(base): +- if self._verbose: +- print("Base path does not exist:", base) +- return -1 +- elif not os.path.exists(merger): +- if self._verbose: +- print("Merger path does not exist") +- return -1 +- +- # paths to be aware of and not overwrite/remove/etc +- staging_path = os.path.join(self._updater_path,"update_staging") +- backup_path = os.path.join(self._updater_path,"backup") +- +- # If clean install is enabled, clear existing files ahead of time +- # note: will not delete the update.json, update folder, staging, or staging +- # but will delete all other folders/files in addon directory +- error = None +- if clean==True: +- try: +- # implement clearing of all folders/files, except the +- # updater folder and updater json +- # Careful, this deletes entire subdirectories recursively... +- # make sure that base is not a high level shared folder, but +- # is dedicated just to the addon itself +- if self._verbose: print("clean=True, clearing addon folder to fresh install state") +- +- # remove root files and folders (except update folder) +- files = [f for f in os.listdir(base) if os.path.isfile(os.path.join(base,f))] +- folders = [f for f in os.listdir(base) if os.path.isdir(os.path.join(base,f))] +- +- for f in files: +- os.remove(os.path.join(base,f)) +- print("Clean removing file {}".format(os.path.join(base,f))) +- for f in folders: +- if os.path.join(base,f)==self._updater_path: continue +- shutil.rmtree(os.path.join(base,f)) +- print("Clean removing folder and contents {}".format(os.path.join(base,f))) +- +- except Exception as err: +- error = "failed to create clean existing addon folder" +- print(error, str(err)) +- +- # Walk through the base addon folder for rules on pre-removing +- # but avoid removing/altering backup and updater file +- for path, dirs, files in os.walk(base): +- # prune ie skip updater folder +- dirs[:] = [d for d in dirs if os.path.join(path,d) not in [self._updater_path]] +- for file in files: +- for ptrn in self.remove_pre_update_patterns: +- if fnmatch.filter([file],ptrn): +- try: +- fl = os.path.join(path,file) +- os.remove(fl) +- if self._verbose: print("Pre-removed file "+file) +- except OSError: +- print("Failed to pre-remove "+file) +- +- # Walk through the temp addon sub folder for replacements +- # this implements the overwrite rules, which apply after +- # the above pre-removal rules. This also performs the +- # actual file copying/replacements +- for path, dirs, files in os.walk(merger): +- # verify this structure works to prune updater sub folder overwriting +- dirs[:] = [d for d in dirs if os.path.join(path,d) not in [self._updater_path]] +- relPath = os.path.relpath(path, merger) +- destPath = os.path.join(base, relPath) +- if not os.path.exists(destPath): +- os.makedirs(destPath) +- for file in files: +- # bring in additional logic around copying/replacing +- # Blender default: overwrite .py's, don't overwrite the rest +- destFile = os.path.join(destPath, file) +- srcFile = os.path.join(path, file) +- +- # decide whether to replace if file already exists, and copy new over +- if os.path.isfile(destFile): +- # otherwise, check each file to see if matches an overwrite pattern +- replaced=False +- for ptrn in self._overwrite_patterns: +- if fnmatch.filter([destFile],ptrn): +- replaced=True +- break +- if replaced: +- os.remove(destFile) +- os.rename(srcFile, destFile) +- if self._verbose: print("Overwrote file "+os.path.basename(destFile)) +- else: +- if self._verbose: print("Pattern not matched to "+os.path.basename(destFile)+", not overwritten") +- else: +- # file did not previously exist, simply move it over +- os.rename(srcFile, destFile) +- if self._verbose: print("New file "+os.path.basename(destFile)) +- +- # now remove the temp staging folder and downloaded zip +- try: +- shutil.rmtree(staging_path) +- except: +- error = "Error: Failed to remove existing staging directory, consider manually removing "+staging_path +- if self._verbose: print(error) +- +- +- def reload_addon(self): +- # if post_update false, skip this function +- # else, unload/reload addon & trigger popup +- if self._auto_reload_post_update == False: +- print("Restart blender to reload addon and complete update") +- return +- +- if self._verbose: print("Reloading addon...") +- addon_utils.modules(refresh=True) +- bpy.utils.refresh_script_paths() +- +- # not allowed in restricted context, such as register module +- # toggle to refresh +- bpy.ops.wm.addon_disable(module=self._addon_package) +- bpy.ops.wm.addon_refresh() +- bpy.ops.wm.addon_enable(module=self._addon_package) +- +- +- # ------------------------------------------------------------------------- +- # Other non-api functions and setups +- # ------------------------------------------------------------------------- +- +- def clear_state(self): +- self._update_ready = None +- self._update_link = None +- self._update_version = None +- self._source_zip = None +- self._error = None +- self._error_msg = None +- +- # custom urlretrieve implementation +- def urlretrieve(self, urlfile, filepath): +- chunk = 1024*8 +- f = open(filepath, "wb") +- while 1: +- data = urlfile.read(chunk) +- if not data: +- #print("done.") +- break +- f.write(data) +- #print("Read %s bytes"%len(data)) +- f.close() +- +- +- def version_tuple_from_text(self,text): +- if text == None: return () +- +- # should go through string and remove all non-integers, +- # and for any given break split into a different section +- segments = [] +- tmp = '' +- for l in str(text): +- if l.isdigit()==False: +- if len(tmp)>0: +- segments.append(int(tmp)) +- tmp = '' +- else: +- tmp+=l +- if len(tmp)>0: +- segments.append(int(tmp)) +- +- if len(segments)==0: +- if self._verbose: print("No version strings found text: ",text) +- if self._include_branches == False: +- return () +- else: +- return (text) +- return tuple(segments) +- +- # called for running check in a background thread +- def check_for_update_async(self, callback=None): +- +- if self._json != None and "update_ready" in self._json and self._json["version_text"]!={}: +- if self._json["update_ready"] == True: +- self._update_ready = True +- self._update_link = self._json["version_text"]["link"] +- self._update_version = str(self._json["version_text"]["version"]) +- # cached update +- callback(True) +- return +- +- # do the check +- if self._check_interval_enable == False: +- return +- elif self._async_checking == True: +- if self._verbose: print("Skipping async check, already started") +- return # already running the bg thread +- elif self._update_ready == None: +- self.start_async_check_update(False, callback) +- +- +- def check_for_update_now(self, callback=None): +- +- self._error = None +- self._error_msg = None +- +- if self._verbose: +- print("Check update pressed, first getting current status") +- if self._async_checking == True: +- if self._verbose: print("Skipping async check, already started") +- return # already running the bg thread +- elif self._update_ready == None: +- self.start_async_check_update(True, callback) +- else: +- self._update_ready = None +- self.start_async_check_update(True, callback) +- +- +- # this function is not async, will always return in sequential fashion +- # but should have a parent which calls it in another thread +- def check_for_update(self, now=False): +- if self._verbose: print("Checking for update function") +- +- # clear the errors if any +- self._error = None +- self._error_msg = None +- +- # avoid running again in, just return past result if found +- # but if force now check, then still do it +- if self._update_ready != None and now == False: +- return (self._update_ready,self._update_version,self._update_link) +- +- if self._current_version == None: +- raise ValueError("current_version not yet defined") +- if self._repo == None: +- raise ValueError("repo not yet defined") +- if self._user == None: +- raise ValueError("username not yet defined") +- +- self.set_updater_json() # self._json +- +- if now == False and self.past_interval_timestamp()==False: +- if self._verbose: +- print("Aborting check for updated, check interval not reached") +- return (False, None, None) +- +- # check if using tags or releases +- # note that if called the first time, this will pull tags from online +- if self._fake_install == True: +- if self._verbose: +- print("fake_install = True, setting fake version as ready") +- self._update_ready = True +- self._update_version = "(999,999,999)" +- self._update_link = "http://127.0.0.1" +- +- return (self._update_ready, self._update_version, self._update_link) +- +- # primary internet call +- self.get_tags() # sets self._tags and self._tag_latest +- +- self._json["last_check"] = str(datetime.now()) +- self.save_updater_json() +- +- # can be () or ('master') in addition to branches, and version tag +- new_version = self.version_tuple_from_text(self.tag_latest) +- +- if len(self._tags)==0: +- self._update_ready = False +- self._update_version = None +- self._update_link = None +- return (False, None, None) +- if self._include_branches == False: +- link = self.select_link(self, self._tags[0]) +- else: +- n = len(self._include_branch_list) +- if len(self._tags)==n: +- # effectively means no tags found on repo +- # so provide the first one as default +- link = self.select_link(self, self._tags[0]) +- else: +- link = self.select_link(self, self._tags[n]) +- +- if new_version == (): +- self._update_ready = False +- self._update_version = None +- self._update_link = None +- return (False, None, None) +- elif str(new_version).lower() in self._include_branch_list: +- # handle situation where master/whichever branch is included +- # however, this code effectively is not triggered now +- # as new_version will only be tag names, not branch names +- if self._include_branch_autocheck == False: +- # don't offer update as ready, +- # but set the link for the default +- # branch for installing +- self._update_ready = False +- self._update_version = new_version +- self._update_link = link +- self.save_updater_json() +- return (True, new_version, link) +- else: +- raise ValueError("include_branch_autocheck: NOT YET DEVELOPED") +- # bypass releases and look at timestamp of last update +- # from a branch compared to now, see if commit values +- # match or not. +- +- else: +- # situation where branches not included +- +- if new_version > self._current_version: +- +- self._update_ready = True +- self._update_version = new_version +- self._update_link = link +- self.save_updater_json() +- return (True, new_version, link) +- +- # elif new_version != self._current_version: +- # self._update_ready = False +- # self._update_version = new_version +- # self._update_link = link +- # self.save_updater_json() +- # return (True, new_version, link) +- +- # if no update, set ready to False from None +- self._update_ready = False +- self._update_version = None +- self._update_link = None +- return (False, None, None) +- +- +- def set_tag(self, name): +- """Assign the tag name and url to update to""" +- tg = None +- for tag in self._tags: +- if name == tag["name"]: +- tg = tag +- break +- if tg: +- new_version = self.version_tuple_from_text(self.tag_latest) +- self._update_version = new_version +- self._update_link = self.select_link(self, tg) +- elif self._include_branches and name in self._include_branch_list: +- # scenario if reverting to a specific branch name instead of tag +- tg = name +- link = self.form_branch_url(tg) +- self._update_version = name # this will break things +- self._update_link = link +- if not tg: +- raise ValueError("Version tag not found: "+name) +- +- +- def run_update(self,force=False,revert_tag=None,clean=False,callback=None): +- """Runs an install, update, or reversion of an addon from online source +- +- Arguments: +- force: Install assigned link, even if self.update_ready is False +- revert_tag: Version to install, if none uses detected update link +- clean: not used, but in future could use to totally refresh addon +- callback: used to run function on update completion +- """ +- self._json["update_ready"] = False +- self._json["ignore"] = False # clear ignore flag +- self._json["version_text"] = {} +- +- if revert_tag != None: +- self.set_tag(revert_tag) +- self._update_ready = True +- +- # clear the errors if any +- self._error = None +- self._error_msg = None +- +- if self._verbose: print("Running update") +- +- if self._fake_install == True: +- # change to True, to trigger the reload/"update installed" handler +- if self._verbose: +- print("fake_install=True") +- print("Just reloading and running any handler triggers") +- self._json["just_updated"] = True +- self.save_updater_json() +- if self._backup_current == True: +- self.create_backup() +- self.reload_addon() +- self._update_ready = False +- res = True # fake "success" zip download flag +- +- elif force==False: +- if self._update_ready != True: +- if self._verbose: +- print("Update stopped, new version not ready") +- if callback: +- callback( +- self._addon_package, +- "Update stopped, new version not ready") +- return "Update stopped, new version not ready" +- elif self._update_link == None: +- # this shouldn't happen if update is ready +- if self._verbose: +- print("Update stopped, update link unavailable") +- if callback: +- callback( +- self._addon_package, +- "Update stopped, update link unavailable") +- return "Update stopped, update link unavailable" +- +- if self._verbose and revert_tag==None: +- print("Staging update") +- elif self._verbose: +- print("Staging install") +- +- res = self.stage_repository(self._update_link) +- if res !=True: +- print("Error in staging repository: "+str(res)) +- if callback != None: +- callback(self._addon_package, self._error_msg) +- return self._error_msg +- res = self.unpack_staged_zip(clean) +- if res<0: +- if callback: +- callback(self._addon_package, self._error_msg) +- return res +- +- else: +- if self._update_link == None: +- if self._verbose: +- print("Update stopped, could not get link") +- return "Update stopped, could not get link" +- if self._verbose: +- print("Forcing update") +- +- res = self.stage_repository(self._update_link) +- if res !=True: +- print("Error in staging repository: "+str(res)) +- if callback: +- callback(self._addon_package, self._error_msg) +- return self._error_msg +- res = self.unpack_staged_zip(clean) +- if res<0: +- return res +- # would need to compare against other versions held in tags +- +- # run the front-end's callback if provided +- if callback: +- callback(self._addon_package) +- +- # return something meaningful, 0 means it worked +- return 0 +- +- +- def past_interval_timestamp(self): +- if self._check_interval_enable == False: +- return True # ie this exact feature is disabled +- +- if "last_check" not in self._json or self._json["last_check"] == "": +- return True +- else: +- now = datetime.now() +- last_check = datetime.strptime(self._json["last_check"], +- "%Y-%m-%d %H:%M:%S.%f") +- next_check = last_check +- offset = timedelta( +- days=self._check_interval_days + 30*self._check_interval_months, +- hours=self._check_interval_hours, +- minutes=self._check_interval_minutes +- ) +- +- delta = (now - offset) - last_check +- if delta.total_seconds() > 0: +- if self._verbose: +- print("{} Updater: Time to check for updates!".format(self._addon)) +- return True +- else: +- if self._verbose: +- print("{} Updater: Determined it's not yet time to check for updates".format(self._addon)) +- return False +- +- def get_json_path(self): +- """Returns the full path to the JSON state file used by this updater. +- +- Will also rename old file paths to addon-specific path if found +- """ +- json_path = os.path.join(self._updater_path, +- "{}_updater_status.json".format(self._addon_package)) +- old_json_path = os.path.join(self._updater_path, "updater_status.json") +- +- # rename old file if it exists +- try: +- os.rename(old_json_path, json_path) +- except FileNotFoundError: +- pass +- except Exception as err: +- print("Other OS error occurred while trying to rename old JSON") +- print(err) +- return json_path +- +- def set_updater_json(self): +- """Load or initialize JSON dictionary data for updater state""" +- if self._updater_path == None: +- raise ValueError("updater_path is not defined") +- elif os.path.isdir(self._updater_path) == False: +- os.makedirs(self._updater_path) +- +- jpath = self.get_json_path() +- if os.path.isfile(jpath): +- with open(jpath) as data_file: +- self._json = json.load(data_file) +- if self._verbose: +- print("{} Updater: Read in JSON settings from file".format( +- self._addon)) +- else: +- # set data structure +- self._json = { +- "last_check":"", +- "backup_date":"", +- "update_ready":False, +- "ignore":False, +- "just_restored":False, +- "just_updated":False, +- "version_text":{} +- } +- self.save_updater_json() +- +- +- def save_updater_json(self): +- # first save the state +- if self._update_ready == True: +- if type(self._update_version) == type((0,0,0)): +- self._json["update_ready"] = True +- self._json["version_text"]["link"]=self._update_link +- self._json["version_text"]["version"]=self._update_version +- else: +- self._json["update_ready"] = False +- self._json["version_text"] = {} +- else: +- self._json["update_ready"] = False +- self._json["version_text"] = {} +- +- jpath = self.get_json_path() +- outf = open(jpath,'w') +- data_out = json.dumps(self._json, indent=4) +- outf.write(data_out) +- outf.close() +- if self._verbose: +- print(self._addon+": Wrote out updater JSON settings to file, with the contents:") +- print(self._json) +- +- def json_reset_postupdate(self): +- self._json["just_updated"] = False +- self._json["update_ready"] = False +- self._json["version_text"] = {} +- self.save_updater_json() +- +- def json_reset_restore(self): +- self._json["just_restored"] = False +- self._json["update_ready"] = False +- self._json["version_text"] = {} +- self.save_updater_json() +- self._update_ready = None # reset so you could check update again +- +- def ignore_update(self): +- self._json["ignore"] = True +- self.save_updater_json() +- +- +- # ------------------------------------------------------------------------- +- # ASYNC stuff +- # ------------------------------------------------------------------------- +- +- def start_async_check_update(self, now=False, callback=None): +- """Start a background thread which will check for updates""" +- if self._async_checking is True: +- return +- if self._verbose: +- print("{} updater: Starting background checking thread".format( +- self._addon)) +- check_thread = threading.Thread(target=self.async_check_update, +- args=(now,callback,)) +- check_thread.daemon = True +- self._check_thread = check_thread +- check_thread.start() +- +- def async_check_update(self, now, callback=None): +- """Perform update check, run as target of background thread""" +- self._async_checking = True +- if self._verbose: +- print("{} BG thread: Checking for update now in background".format( +- self._addon)) +- +- try: +- self.check_for_update(now=now) +- except Exception as exception: +- print("Checking for update error:") +- print(exception) +- if not self._error: +- self._update_ready = False +- self._update_version = None +- self._update_link = None +- self._error = "Error occurred" +- self._error_msg = "Encountered an error while checking for updates" +- +- self._async_checking = False +- self._check_thread = None +- +- if self._verbose: +- print("{} BG thread: Finished checking for update, doing callback".format(self._addon)) +- if callback: +- callback(self._update_ready) +- +- def stop_async_check_update(self): +- """Method to give impression of stopping check for update. +- +- Currently does nothing but allows user to retry/stop blocking UI from +- hitting a refresh button. This does not actually stop the thread, as it +- will complete after the connection timeout regardless. If the thread +- does complete with a successful response, this will be still displayed +- on next UI refresh (ie no update, or update available). +- """ +- if self._check_thread != None: +- if self._verbose: print("Thread will end in normal course.") +- # however, "There is no direct kill method on a thread object." +- # better to let it run its course +- #self._check_thread.stop() +- self._async_checking = False +- self._error = None +- self._error_msg = None +- +- +-# ----------------------------------------------------------------------------- +-# Updater Engines +-# ----------------------------------------------------------------------------- +- +- +-class BitbucketEngine(object): +- """Integration to Bitbucket API for git-formatted repositories""" +- +- def __init__(self): +- self.api_url = 'https://api.bitbucket.org' +- self.token = None +- self.name = "bitbucket" +- +- def form_repo_url(self, updater): +- return self.api_url+"/2.0/repositories/"+updater.user+"/"+updater.repo +- +- def form_tags_url(self, updater): +- return self.form_repo_url(updater) + "/refs/tags?sort=-name" +- +- def form_branch_url(self, branch, updater): +- return self.get_zip_url(branch, updater) +- +- def get_zip_url(self, name, updater): +- return "https://bitbucket.org/{user}/{repo}/get/{name}.zip".format( +- user=updater.user, +- repo=updater.repo, +- name=name) +- +- def parse_tags(self, response, updater): +- if response == None: +- return [] +- return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["name"], updater)} for tag in response["values"]] +- +- +-class GithubEngine(object): +- """Integration to Github API""" +- +- def __init__(self): +- self.api_url = 'https://api.github.com' +- self.token = None +- self.name = "github" +- +- def form_repo_url(self, updater): +- return "{}{}{}{}{}".format(self.api_url,"/repos/",updater.user, +- "/",updater.repo) +- +- def form_tags_url(self, updater): +- if updater.use_releases: +- return "{}{}".format(self.form_repo_url(updater),"/releases") +- else: +- return "{}{}".format(self.form_repo_url(updater),"/tags") +- +- def form_branch_list_url(self, updater): +- return "{}{}".format(self.form_repo_url(updater),"/branches") +- +- def form_branch_url(self, branch, updater): +- return "{}{}{}".format(self.form_repo_url(updater), +- "/zipball/",branch) +- +- def parse_tags(self, response, updater): +- if response == None: +- return [] +- return response +- +- +-class GitlabEngine(object): +- """Integration to GitLab API""" +- +- def __init__(self): +- self.api_url = 'https://gitlab.com' +- self.token = None +- self.name = "gitlab" +- +- def form_repo_url(self, updater): +- return "{}{}{}".format(self.api_url,"/api/v4/projects/",updater.repo) +- +- def form_tags_url(self, updater): +- return "{}{}".format(self.form_repo_url(updater),"/repository/tags") +- +- def form_branch_list_url(self, updater): +- # does not validate branch name. +- return "{}{}".format( +- self.form_repo_url(updater), +- "/repository/branches") +- +- def form_branch_url(self, branch, updater): +- # Could clash with tag names and if it does, it will +- # download TAG zip instead of branch zip to get +- # direct path, would need. +- return "{}{}{}".format( +- self.form_repo_url(updater), +- "/repository/archive.zip?sha=", +- branch) +- +- def get_zip_url(self, sha, updater): +- return "{base}/repository/archive.zip?sha={sha}".format( +- base=self.form_repo_url(updater), +- sha=sha) +- +- # def get_commit_zip(self, id, updater): +- # return self.form_repo_url(updater)+"/repository/archive.zip?sha:"+id +- +- def parse_tags(self, response, updater): +- if response == None: +- return [] +- return [{"name": tag["name"], "zipball_url": self.get_zip_url(tag["commit"]["id"], updater)} for tag in response] +- +- +-# ----------------------------------------------------------------------------- +-# The module-shared class instance, +-# should be what's imported to other files +-# ----------------------------------------------------------------------------- +- +-Updater = Singleton_updater() +diff --git a/addon_updater_ops.py b/addon_updater_ops.py +deleted file mode 100644 +index 5ac1c9c..0000000 +--- a/addon_updater_ops.py ++++ /dev/null +@@ -1,1443 +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 ##### +- +-import os +- +-import bpy +-from bpy.app.handlers import persistent +- +-# updater import, import safely +-# Prevents popups for users with invalid python installs e.g. missing libraries +-try: +- from .addon_updater import Updater as updater +-except Exception as e: +- print("ERROR INITIALIZING UPDATER") +- print(str(e)) +- class Singleton_updater_none(object): +- def __init__(self): +- self.addon = None +- self.verbose = False +- self.invalidupdater = True # used to distinguish bad install +- self.error = None +- self.error_msg = None +- self.async_checking = None +- def clear_state(self): +- self.addon = None +- self.verbose = False +- self.invalidupdater = True +- self.error = None +- self.error_msg = None +- self.async_checking = None +- def run_update(self): pass +- def check_for_update(self): pass +- updater = Singleton_updater_none() +- updater.error = "Error initializing updater module" +- updater.error_msg = str(e) +- +-# Must declare this before classes are loaded +-# otherwise the bl_idname's will not match and have errors. +-# Must be all lowercase and no spaces +-updater.addon = "gaffer" +- +- +-# ----------------------------------------------------------------------------- +-# Blender version utils +-# ----------------------------------------------------------------------------- +- +- +-def make_annotations(cls): +- """Add annotation attribute to class fields to avoid Blender 2.8 warnings""" +- if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80): +- return cls +- bl_props = {k: v for k, v in cls.__dict__.items() if isinstance(v, tuple)} +- if bl_props: +- if '__annotations__' not in cls.__dict__: +- setattr(cls, '__annotations__', {}) +- annotations = cls.__dict__['__annotations__'] +- for k, v in bl_props.items(): +- annotations[k] = v +- delattr(cls, k) +- return cls +- +- +-def layout_split(layout, factor=0.0, align=False): +- """Intermediate method for pre and post blender 2.8 split UI function""" +- if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80): +- return layout.split(percentage=factor, align=align) +- return layout.split(factor=factor, align=align) +- +- +-def get_user_preferences(context=None): +- """Intermediate method for pre and post blender 2.8 grabbing preferences""" +- if not context: +- context = bpy.context +- prefs = None +- if hasattr(context, "user_preferences"): +- prefs = context.user_preferences.addons.get(__package__, None) +- elif hasattr(context, "preferences"): +- prefs = context.preferences.addons.get(__package__, None) +- if prefs: +- return prefs.preferences +- # To make the addon stable and non-exception prone, return None +- # raise Exception("Could not fetch user preferences") +- return None +- +- +-# ----------------------------------------------------------------------------- +-# Updater operators +-# ----------------------------------------------------------------------------- +- +- +-# simple popup for prompting checking for update & allow to install if available +-class addon_updater_install_popup(bpy.types.Operator): +- """Check and install update if available""" +- bl_label = "Update {x} addon".format(x=updater.addon) +- bl_idname = updater.addon+".updater_install_popup" +- bl_description = "Popup menu to check and display current updates available" +- bl_options = {'REGISTER', 'INTERNAL'} +- +- # if true, run clean install - ie remove all files before adding new +- # equivalent to deleting the addon and reinstalling, except the +- # updater folder/backup folder remains +- clean_install = bpy.props.BoolProperty( +- name="Clean install", +- description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", +- default=False, +- options={'HIDDEN'} +- ) +- ignore_enum = bpy.props.EnumProperty( +- name="Process update", +- description="Decide to install, ignore, or defer new addon update", +- items=[ +- ("install","Update Now","Install update now"), +- ("ignore","Ignore", "Ignore this update to prevent future popups"), +- ("defer","Defer","Defer choice till next blender session") +- ], +- options={'HIDDEN'} +- ) +- +- def check (self, context): +- return True +- +- def invoke(self, context, event): +- return context.window_manager.invoke_props_dialog(self) +- +- def draw(self, context): +- layout = self.layout +- if updater.invalidupdater == True: +- layout.label(text="Updater module error") +- return +- elif updater.update_ready == True: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Update {} ready!".format(str(updater.update_version)), +- icon="LOOP_FORWARDS") +- col.label(text="Choose 'Update Now' & press OK to install, ",icon="BLANK1") +- col.label(text="or click outside window to defer",icon="BLANK1") +- row = col.row() +- row.prop(self,"ignore_enum",expand=True) +- col.split() +- elif updater.update_ready == False: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="No updates available") +- col.label(text="Press okay to dismiss dialog") +- # add option to force install +- else: +- # case: updater.update_ready = None +- # we have not yet checked for the update +- layout.label(text="Check for update now?") +- +- # potentially in future, could have UI for 'check to select old version' +- # to revert back to. +- +- def execute(self,context): +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- +- if updater.manual_only==True: +- bpy.ops.wm.url_open(url=updater.website) +- elif updater.update_ready == True: +- +- # action based on enum selection +- if self.ignore_enum=='defer': +- return {'FINISHED'} +- elif self.ignore_enum=='ignore': +- updater.ignore_update() +- return {'FINISHED'} +- #else: "install update now!" +- +- res = updater.run_update( +- force=False, +- callback=post_update_callback, +- clean=self.clean_install) +- # should return 0, if not something happened +- if updater.verbose: +- if res==0: +- print("Updater returned successful") +- else: +- print("Updater returned {}, error occurred".format(res)) +- elif updater.update_ready == None: +- _ = updater.check_for_update(now=True) +- +- # re-launch this dialog +- atr = addon_updater_install_popup.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- else: +- if updater.verbose: +- print("Doing nothing, not ready for update") +- return {'FINISHED'} +- +- +-# User preference check-now operator +-class addon_updater_check_now(bpy.types.Operator): +- bl_label = "Check now for "+updater.addon+" update" +- bl_idname = updater.addon+".updater_check_now" +- bl_description = "Check now for an update to the {x} addon".format( +- x=updater.addon) +- bl_options = {'REGISTER', 'INTERNAL'} +- +- def execute(self,context): +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- +- if updater.async_checking == True and updater.error == None: +- # Check already happened +- # Used here to just avoid constant applying settings below +- # Ignoring if error, to prevent being stuck on the error screen +- return {'CANCELLED'} +- +- # apply the UI settings +- settings = get_user_preferences(context) +- if not settings: +- if updater.verbose: +- print("Could not get {} preferences, update check skipped".format( +- __package__)) +- return {'CANCELLED'} +- updater.set_check_interval(enable=settings.auto_check_update, +- months=settings.updater_intrval_months, +- days=settings.updater_intrval_days, +- hours=settings.updater_intrval_hours, +- minutes=settings.updater_intrval_minutes +- ) # optional, if auto_check_update +- +- # input is an optional callback function +- # this function should take a bool input, if true: update ready +- # if false, no update ready +- updater.check_for_update_now(ui_refresh) +- +- return {'FINISHED'} +- +- +-class addon_updater_update_now(bpy.types.Operator): +- bl_label = "Update "+updater.addon+" addon now" +- bl_idname = updater.addon+".updater_update_now" +- bl_description = "Update to the latest version of the {x} addon".format( +- x=updater.addon) +- bl_options = {'REGISTER', 'INTERNAL'} +- +- # if true, run clean install - ie remove all files before adding new +- # equivalent to deleting the addon and reinstalling, except the +- # updater folder/backup folder remains +- clean_install = bpy.props.BoolProperty( +- name="Clean install", +- description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", +- default=False, +- options={'HIDDEN'} +- ) +- +- def execute(self,context): +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- +- if updater.manual_only == True: +- bpy.ops.wm.url_open(url=updater.website) +- if updater.update_ready == True: +- # if it fails, offer to open the website instead +- try: +- res = updater.run_update( +- force=False, +- callback=post_update_callback, +- clean=self.clean_install) +- +- # should return 0, if not something happened +- if updater.verbose: +- if res==0: print("Updater returned successful") +- else: print("Updater returned "+str(res)+", error occurred") +- except Exception as e: +- updater._error = "Error trying to run update" +- updater._error_msg = str(e) +- atr = addon_updater_install_manually.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- elif updater.update_ready == None: +- (update_ready, version, link) = updater.check_for_update(now=True) +- # re-launch this dialog +- atr = addon_updater_install_popup.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- +- elif updater.update_ready == False: +- self.report({'INFO'}, "Nothing to update") +- else: +- self.report({'ERROR'}, "Encountered problem while trying to update") +- +- return {'FINISHED'} +- +- +-class addon_updater_update_target(bpy.types.Operator): +- bl_label = updater.addon+" version target" +- bl_idname = updater.addon+".updater_update_target" +- bl_description = "Install a targeted version of the {x} addon".format( +- x=updater.addon) +- bl_options = {'REGISTER', 'INTERNAL'} +- +- def target_version(self, context): +- # in case of error importing updater +- if updater.invalidupdater == True: +- ret = [] +- +- ret = [] +- i=0 +- for tag in updater.tags: +- ret.append( (tag,tag,"Select to install "+tag) ) +- i+=1 +- return ret +- +- target = bpy.props.EnumProperty( +- name="Target version to install", +- description="Select the version to install", +- items=target_version +- ) +- +- # if true, run clean install - ie remove all files before adding new +- # equivalent to deleting the addon and reinstalling, except the +- # updater folder/backup folder remains +- clean_install = bpy.props.BoolProperty( +- name="Clean install", +- description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", +- default=False, +- options={'HIDDEN'} +- ) +- +- @classmethod +- def poll(cls, context): +- if updater.invalidupdater == True: return False +- return updater.update_ready != None and len(updater.tags)>0 +- +- def invoke(self, context, event): +- return context.window_manager.invoke_props_dialog(self) +- +- def draw(self, context): +- layout = self.layout +- if updater.invalidupdater == True: +- layout.label(text="Updater error") +- return +- split = layout_split(layout, factor=0.66) +- subcol = split.column() +- subcol.label(text="Select install version") +- subcol = split.column() +- subcol.prop(self, "target", text="") +- +- +- def execute(self,context): +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- +- res = updater.run_update( +- force=False, +- revert_tag=self.target, +- callback=post_update_callback, +- clean=self.clean_install) +- +- # should return 0, if not something happened +- if res==0: +- if updater.verbose: +- print("Updater returned successful") +- else: +- if updater.verbose: +- print("Updater returned "+str(res)+", error occurred") +- return {'CANCELLED'} +- +- return {'FINISHED'} +- +- +-class addon_updater_install_manually(bpy.types.Operator): +- """As a fallback, direct the user to download the addon manually""" +- bl_label = "Install update manually" +- bl_idname = updater.addon+".updater_install_manually" +- bl_description = "Proceed to manually install update" +- bl_options = {'REGISTER', 'INTERNAL'} +- +- error = bpy.props.StringProperty( +- name="Error Occurred", +- default="", +- options={'HIDDEN'} +- ) +- +- def invoke(self, context, event): +- return context.window_manager.invoke_popup(self) +- +- def draw(self, context): +- layout = self.layout +- +- if updater.invalidupdater == True: +- layout.label(text="Updater error") +- return +- +- # use a "failed flag"? it shows this label if the case failed. +- if self.error!="": +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="There was an issue trying to auto-install",icon="ERROR") +- col.label(text="Press the download button below and install",icon="BLANK1") +- col.label(text="the zip file like a normal addon.",icon="BLANK1") +- else: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Install the addon manually") +- col.label(text="Press the download button below and install") +- col.label(text="the zip file like a normal addon.") +- +- # if check hasn't happened, i.e. accidentally called this menu +- # allow to check here +- +- row = layout.row() +- +- if updater.update_link != None: +- row.operator("wm.url_open", +- text="Direct download").url=updater.update_link +- else: +- row.operator("wm.url_open", +- text="(failed to retrieve direct download)") +- row.enabled = False +- +- if updater.website != None: +- row = layout.row() +- row.operator("wm.url_open",text="Open website").url=\ +- updater.website +- else: +- row = layout.row() +- row.label(text="See source website to download the update") +- +- def execute(self,context): +- +- return {'FINISHED'} +- +- +-class addon_updater_updated_successful(bpy.types.Operator): +- """Addon in place, popup telling user it completed or what went wrong""" +- bl_label = "Installation Report" +- bl_idname = updater.addon+".updater_update_successful" +- bl_description = "Update installation response" +- bl_options = {'REGISTER', 'INTERNAL', 'UNDO'} +- +- error = bpy.props.StringProperty( +- name="Error Occurred", +- default="", +- options={'HIDDEN'} +- ) +- +- def invoke(self, context, event): +- return context.window_manager.invoke_props_popup(self, event) +- +- def draw(self, context): +- layout = self.layout +- +- if updater.invalidupdater == True: +- layout.label(text="Updater error") +- return +- +- saved = updater.json +- if self.error != "": +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Error occurred, did not install", icon="ERROR") +- if updater.error_msg: +- msg = updater.error_msg +- else: +- msg = self.error +- col.label(str(msg), icon="BLANK1") +- rw = col.row() +- rw.scale_y = 2 +- rw.operator("wm.url_open", +- text="Click for manual download.", +- icon="BLANK1" +- ).url=updater.website +- # manual download button here +- elif updater.auto_reload_post_update == False: +- # tell user to restart blender +- if "just_restored" in saved and saved["just_restored"] == True: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Addon restored", icon="RECOVER_LAST") +- col.label(text="Restart blender to reload.",icon="BLANK1") +- updater.json_reset_restore() +- else: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Addon successfully installed", icon="FILE_TICK") +- col.label(text="Restart blender to reload.", icon="BLANK1") +- +- else: +- # reload addon, but still recommend they restart blender +- if "just_restored" in saved and saved["just_restored"] == True: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Addon restored", icon="RECOVER_LAST") +- col.label(text="Consider restarting blender to fully reload.", +- icon="BLANK1") +- updater.json_reset_restore() +- else: +- col = layout.column() +- col.scale_y = 0.7 +- col.label(text="Addon successfully installed", icon="FILE_TICK") +- col.label(text="Consider restarting blender to fully reload.", +- icon="BLANK1") +- +- def execute(self, context): +- return {'FINISHED'} +- +- +-class addon_updater_restore_backup(bpy.types.Operator): +- """Restore addon from backup""" +- bl_label = "Restore backup" +- bl_idname = updater.addon+".updater_restore_backup" +- bl_description = "Restore addon from backup" +- bl_options = {'REGISTER', 'INTERNAL'} +- +- @classmethod +- def poll(cls, context): +- try: +- return os.path.isdir(os.path.join(updater.stage_path,"backup")) +- except: +- return False +- +- def execute(self, context): +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- updater.restore_backup() +- return {'FINISHED'} +- +- +-class addon_updater_ignore(bpy.types.Operator): +- """Prevent future update notice popups""" +- bl_label = "Ignore update" +- bl_idname = updater.addon+".updater_ignore" +- bl_description = "Ignore update to prevent future popups" +- bl_options = {'REGISTER', 'INTERNAL'} +- +- @classmethod +- def poll(cls, context): +- if updater.invalidupdater == True: +- return False +- elif updater.update_ready == True: +- return True +- else: +- return False +- +- def execute(self, context): +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- updater.ignore_update() +- self.report({"INFO"},"Open addon preferences for updater options") +- return {'FINISHED'} +- +- +-class addon_updater_end_background(bpy.types.Operator): +- """Stop checking for update in the background""" +- bl_label = "End background check" +- bl_idname = updater.addon+".end_background_check" +- bl_description = "Stop checking for update in the background" +- bl_options = {'REGISTER', 'INTERNAL'} +- +- # @classmethod +- # def poll(cls, context): +- # if updater.async_checking == True: +- # return True +- # else: +- # return False +- +- def execute(self, context): +- # in case of error importing updater +- if updater.invalidupdater == True: +- return {'CANCELLED'} +- updater.stop_async_check_update() +- return {'FINISHED'} +- +- +-# ----------------------------------------------------------------------------- +-# Handler related, to create popups +-# ----------------------------------------------------------------------------- +- +- +-# global vars used to prevent duplicate popup handlers +-ran_autocheck_install_popup = False +-ran_update_sucess_popup = False +- +-# global var for preventing successive calls +-ran_background_check = False +- +-@persistent +-def updater_run_success_popup_handler(scene): +- global ran_update_sucess_popup +- ran_update_sucess_popup = True +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return +- +- try: +- bpy.app.handlers.scene_update_post.remove( +- updater_run_success_popup_handler) +- except: +- pass +- +- atr = addon_updater_updated_successful.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- +- +-@persistent +-def updater_run_install_popup_handler(scene): +- global ran_autocheck_install_popup +- ran_autocheck_install_popup = True +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return +- +- try: +- bpy.app.handlers.scene_update_post.remove( +- updater_run_install_popup_handler) +- except: +- pass +- +- if "ignore" in updater.json and updater.json["ignore"] == True: +- return # don't do popup if ignore pressed +- # elif type(updater.update_version) != type((0,0,0)): +- # # likely was from master or another branch, shouldn't trigger popup +- # updater.json_reset_restore() +- # return +- elif "version_text" in updater.json and "version" in updater.json["version_text"]: +- version = updater.json["version_text"]["version"] +- ver_tuple = updater.version_tuple_from_text(version) +- +- if ver_tuple < updater.current_version: +- # user probably manually installed to get the up to date addon +- # in here. Clear out the update flag using this function +- if updater.verbose: +- print("{} updater: appears user updated, clearing flag".format(\ +- updater.addon)) +- updater.json_reset_restore() +- return +- atr = addon_updater_install_popup.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- +- +-def background_update_callback(update_ready): +- """Passed into the updater, background thread updater""" +- global ran_autocheck_install_popup +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return +- if updater.showpopups == False: +- return +- if update_ready != True: +- return +- if updater_run_install_popup_handler not in \ +- bpy.app.handlers.scene_update_post and \ +- ran_autocheck_install_popup==False: +- bpy.app.handlers.scene_update_post.append( +- updater_run_install_popup_handler) +- ran_autocheck_install_popup = True +- +- +-def post_update_callback(module_name, res=None): +- """Callback for once the run_update function has completed +- +- Only makes sense to use this if "auto_reload_post_update" == False, +- i.e. don't auto-restart the addon +- +- Arguments: +- module_name: returns the module name from updater, but unused here +- res: If an error occurred, this is the detail string +- """ +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- return +- +- if res==None: +- # this is the same code as in conditional at the end of the register function +- # ie if "auto_reload_post_update" == True, comment out this code +- if updater.verbose: +- print("{} updater: Running post update callback".format(updater.addon)) +- #bpy.app.handlers.scene_update_post.append(updater_run_success_popup_handler) +- +- atr = addon_updater_updated_successful.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- global ran_update_sucess_popup +- ran_update_sucess_popup = True +- else: +- # some kind of error occurred and it was unable to install, +- # offer manual download instead +- atr = addon_updater_updated_successful.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT',error=res) +- return +- +- +-def ui_refresh(update_status): +- # find a way to just re-draw self? +- # callback intended for trigger by async thread +- for windowManager in bpy.data.window_managers: +- for window in windowManager.windows: +- for area in window.screen.areas: +- area.tag_redraw() +- +- +-def check_for_update_background(): +- """Function for asynchronous background check. +- +- *Could* be called on register, but would be bad practice. +- """ +- if updater.invalidupdater == True: +- return +- global ran_background_check +- if ran_background_check == True: +- # Global var ensures check only happens once +- return +- elif updater.update_ready != None or updater.async_checking == True: +- # Check already happened +- # Used here to just avoid constant applying settings below +- return +- +- # apply the UI settings +- settings = get_user_preferences(bpy.context) +- if not settings: +- return +- updater.set_check_interval(enable=settings.auto_check_update, +- months=settings.updater_intrval_months, +- days=settings.updater_intrval_days, +- hours=settings.updater_intrval_hours, +- minutes=settings.updater_intrval_minutes +- ) # optional, if auto_check_update +- +- # input is an optional callback function +- # this function should take a bool input, if true: update ready +- # if false, no update ready +- if updater.verbose: +- print("{} updater: Running background check for update".format(\ +- updater.addon)) +- updater.check_for_update_async(background_update_callback) +- ran_background_check = True +- +- +-def check_for_update_nonthreaded(self, context): +- """Can be placed in front of other operators to launch when pressed""" +- if updater.invalidupdater == True: +- return +- +- # only check if it's ready, ie after the time interval specified +- # should be the async wrapper call here +- settings = get_user_preferences(bpy.context) +- if not settings: +- if updater.verbose: +- print("Could not get {} preferences, update check skipped".format( +- __package__)) +- return +- updater.set_check_interval(enable=settings.auto_check_update, +- months=settings.updater_intrval_months, +- days=settings.updater_intrval_days, +- hours=settings.updater_intrval_hours, +- minutes=settings.updater_intrval_minutes +- ) # optional, if auto_check_update +- +- (update_ready, version, link) = updater.check_for_update(now=False) +- if update_ready == True: +- atr = addon_updater_install_popup.bl_idname.split(".") +- getattr(getattr(bpy.ops, atr[0]),atr[1])('INVOKE_DEFAULT') +- else: +- if updater.verbose: print("No update ready") +- self.report({'INFO'}, "No update ready") +- +- +-def showReloadPopup(): +- """For use in register only, to show popup after re-enabling the addon +- +- Must be enabled by developer +- """ +- if updater.invalidupdater == True: +- return +- saved_state = updater.json +- global ran_update_sucess_popup +- +- a = saved_state != None +- b = "just_updated" in saved_state +- c = saved_state["just_updated"] +- +- if a and b and c: +- updater.json_reset_postupdate() # so this only runs once +- +- # no handlers in this case +- if updater.auto_reload_post_update == False: return +- +- if updater_run_success_popup_handler not in \ +- bpy.app.handlers.scene_update_post \ +- and ran_update_sucess_popup==False: +- bpy.app.handlers.scene_update_post.append( +- updater_run_success_popup_handler) +- ran_update_sucess_popup = True +- +- +-# ----------------------------------------------------------------------------- +-# Example UI integrations +-# ----------------------------------------------------------------------------- +- +- +-def update_notice_box_ui(self, context): +- """ Panel - Update Available for placement at end/beginning of panel +- +- After a check for update has occurred, this function will draw a box +- saying an update is ready, and give a button for: update now, open website, +- or ignore popup. Ideal to be placed at the end / beginning of a panel +- """ +- +- if updater.invalidupdater == True: +- return +- +- saved_state = updater.json +- if updater.auto_reload_post_update == False: +- if "just_updated" in saved_state and saved_state["just_updated"] == True: +- layout = self.layout +- box = layout.box() +- col = box.column() +- col.scale_y = 0.7 +- col.label(text="Restart blender", icon="ERROR") +- col.label(text="to complete update") +- return +- +- # if user pressed ignore, don't draw the box +- if "ignore" in updater.json and updater.json["ignore"] == True: +- return +- if updater.update_ready != True: +- return +- +- layout = self.layout +- box = layout.box() +- col = box.column(align=True) +- col.label(text="Update ready!",icon="ERROR") +- col.separator() +- row = col.row(align=True) +- split = row.split(align=True) +- colL = split.column(align=True) +- colL.scale_y = 1.5 +- colL.operator(addon_updater_ignore.bl_idname,icon="X",text="Ignore") +- colR = split.column(align=True) +- colR.scale_y = 1.5 +- if updater.manual_only==False: +- colR.operator(addon_updater_update_now.bl_idname, +- text="Update", icon="LOOP_FORWARDS") +- col.operator("wm.url_open", text="Open website").url = updater.website +- #col.operator("wm.url_open",text="Direct download").url=updater.update_link +- col.operator(addon_updater_install_manually.bl_idname, +- text="Install manually") +- else: +- #col.operator("wm.url_open",text="Direct download").url=updater.update_link +- col.operator("wm.url_open", text="Get it now").url = updater.website +- +- +-def update_settings_ui(self, context, element=None): +- """Preferences - for drawing with full width inside user preferences +- +- Create a function that can be run inside user preferences panel for prefs UI +- Place inside UI draw using: addon_updater_ops.updaterSettingsUI(self, context) +- or by: addon_updater_ops.updaterSettingsUI(context) +- """ +- +- # element is a UI element, such as layout, a row, column, or box +- if element==None: +- element = self.layout +- box = element.box() +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- box.label(text="Error initializing updater code:") +- box.label(text=updater.error_msg) +- return +- settings = get_user_preferences(context) +- if not settings: +- box.label(text="Error getting updater preferences", icon='ERROR') +- return +- +- # auto-update settings +- row = box.row() +- row.alignment = 'LEFT' +- row.prop(settings, 'updater_expand_prefs', text="Updates", icon="TRIA_DOWN" if settings.updater_expand_prefs else "TRIA_RIGHT", emboss=False) +- +- if settings.updater_expand_prefs: +- row = box.row() +- +- # special case to tell user to restart blender, if set that way +- if updater.auto_reload_post_update == False: +- saved_state = updater.json +- if "just_updated" in saved_state and saved_state["just_updated"] == True: +- row.label(text="Restart blender to complete update", icon="ERROR") +- return +- +- split = layout_split(row, factor=0.3) +- subcol = split.column() +- subcol.prop(settings, "auto_check_update") +- subcol = split.column() +- +- if settings.auto_check_update==False: +- subcol.enabled = False +- subrow = subcol.row() +- subrow.label(text="Interval between checks") +- subrow = subcol.row(align=True) +- checkcol = subrow.column(align=True) +- checkcol.prop(settings,"updater_intrval_months") +- checkcol = subrow.column(align=True) +- checkcol.prop(settings,"updater_intrval_days") +- checkcol = subrow.column(align=True) +- checkcol.prop(settings,"updater_intrval_hours") +- checkcol = subrow.column(align=True) +- checkcol.prop(settings,"updater_intrval_minutes") +- +- # checking / managing updates +- row = box.row() +- col = row.column() +- if updater.error != None: +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- if "ssl" in updater.error_msg.lower(): +- split.enabled = True +- split.operator(addon_updater_install_manually.bl_idname, +- text=updater.error) +- else: +- split.enabled = False +- split.operator(addon_updater_check_now.bl_idname, +- text=updater.error) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready == None and updater.async_checking == False: +- col.scale_y = 2 +- col.operator(addon_updater_check_now.bl_idname) +- elif updater.update_ready == None: # async is running +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.enabled = False +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text="Checking...") +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_end_background.bl_idname, +- text = "", icon="X") +- +- elif updater.include_branches==True and \ +- len(updater.tags)==len(updater.include_branch_list) and \ +- updater.manual_only==False: +- # no releases found, but still show the appropriate branch +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_update_now.bl_idname, +- text="Update directly to "+str(updater.include_branch_list[0])) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready==True and updater.manual_only==False: +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_update_now.bl_idname, +- text="Update now to "+str(updater.update_version)) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready==True and updater.manual_only==True: +- col.scale_y = 2 +- col.operator("wm.url_open", +- text="Download "+str(updater.update_version)).url=updater.website +- else: # i.e. that updater.update_ready == False +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.enabled = False +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text="Addon is up to date") +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- if updater.manual_only == False: +- col = row.column(align=True) +- #col.operator(addon_updater_update_target.bl_idname, +- if updater.include_branches == True and len(updater.include_branch_list)>0: +- branch = updater.include_branch_list[0] +- col.operator(addon_updater_update_target.bl_idname, +- text="Install latest {} / old version".format(branch)) +- else: +- col.operator(addon_updater_update_target.bl_idname, +- text="Reinstall / install old version") +- lastdate = "none found" +- backuppath = os.path.join(updater.stage_path,"backup") +- if "backup_date" in updater.json and os.path.isdir(backuppath): +- if updater.json["backup_date"] == "": +- lastdate = "Date not found" +- else: +- lastdate = updater.json["backup_date"] +- backuptext = "Restore addon backup ({})".format(lastdate) +- col.operator(addon_updater_restore_backup.bl_idname, text=backuptext) +- +- row = box.row() +- row.scale_y = 0.7 +- lastcheck = updater.json["last_check"] +- if updater.error != None and updater.error_msg != None: +- row.label(text=updater.error_msg) +- elif lastcheck != "" and lastcheck != None: +- lastcheck = lastcheck[0: lastcheck.index(".") ] +- row.label(text="Last update check: " + lastcheck) +- else: +- row.label(text="Last update check: Never") +- +- +-def update_settings_ui_condensed(self, context, element=None): +- """Preferences - Condensed drawing within preferences +- +- Alternate draw for user preferences or other places, does not draw a box +- """ +- +- # element is a UI element, such as layout, a row, column, or box +- if element==None: +- element = self.layout +- row = element.row() +- +- # in case of error importing updater +- if updater.invalidupdater == True: +- row.label(text="Error initializing updater code:") +- row.label(text=updater.error_msg) +- return +- settings = get_user_preferences(context) +- if not settings: +- row.label(text="Error getting updater preferences", icon='ERROR') +- return +- +- # special case to tell user to restart blender, if set that way +- if updater.auto_reload_post_update == False: +- saved_state = updater.json +- if "just_updated" in saved_state and saved_state["just_updated"] == True: +- row.label(text="Restart blender to complete update", icon="ERROR") +- return +- +- col = row.column() +- if updater.error != None: +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- if "ssl" in updater.error_msg.lower(): +- split.enabled = True +- split.operator(addon_updater_install_manually.bl_idname, +- text=updater.error) +- else: +- split.enabled = False +- split.operator(addon_updater_check_now.bl_idname, +- text=updater.error) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready == None and updater.async_checking == False: +- col.scale_y = 2 +- col.operator(addon_updater_check_now.bl_idname) +- elif updater.update_ready == None: # async is running +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.enabled = False +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text="Checking...") +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_end_background.bl_idname, +- text = "", icon="X") +- +- elif updater.include_branches==True and \ +- len(updater.tags)==len(updater.include_branch_list) and \ +- updater.manual_only==False: +- # no releases found, but still show the appropriate branch +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_update_now.bl_idname, +- text="Update directly to "+str(updater.include_branch_list[0])) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready==True and updater.manual_only==False: +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_update_now.bl_idname, +- text="Update now to "+str(updater.update_version)) +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- elif updater.update_ready==True and updater.manual_only==True: +- col.scale_y = 2 +- col.operator("wm.url_open", +- text="Download "+str(updater.update_version)).url=updater.website +- else: # i.e. that updater.update_ready == False +- subcol = col.row(align=True) +- subcol.scale_y = 1 +- split = subcol.split(align=True) +- split.enabled = False +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text="Addon is up to date") +- split = subcol.split(align=True) +- split.scale_y = 2 +- split.operator(addon_updater_check_now.bl_idname, +- text = "", icon="FILE_REFRESH") +- +- row = element.row() +- row.prop(settings, "auto_check_update") +- +- row = element.row() +- row.scale_y = 0.7 +- lastcheck = updater.json["last_check"] +- if updater.error != None and updater.error_msg != None: +- row.label(text=updater.error_msg) +- elif lastcheck != "" and lastcheck != None: +- lastcheck = lastcheck[0: lastcheck.index(".") ] +- row.label(text="Last check: " + lastcheck) +- else: +- row.label(text="Last check: Never") +- +- +-def skip_tag_function(self, tag): +- """A global function for tag skipping +- +- A way to filter which tags are displayed, +- e.g. to limit downgrading too far +- input is a tag text, e.g. "v1.2.3" +- output is True for skipping this tag number, +- False if the tag is allowed (default for all) +- Note: here, "self" is the acting updater shared class instance +- """ +- +- # in case of error importing updater +- if self.invalidupdater == True: +- return False +- +- # ---- write any custom code here, return true to disallow version ---- # +- # +- # # Filter out e.g. if 'beta' is in name of release +- # if 'beta' in tag.lower(): +- # return True +- # ---- write any custom code above, return true to disallow version --- # +- +- if self.include_branches == True: +- for branch in self.include_branch_list: +- if tag["name"].lower() == branch: return False +- +- # function converting string to tuple, ignoring e.g. leading 'v' +- tupled = self.version_tuple_from_text(tag["name"]) +- if type(tupled) != type( (1,2,3) ): return True +- +- # select the min tag version - change tuple accordingly +- if self.version_min_update != None: +- if tupled < self.version_min_update: +- return True # skip if current version below this +- +- # select the max tag version +- if self.version_max_update != None: +- if tupled >= self.version_max_update: +- return True # skip if current version at or above this +- +- # in all other cases, allow showing the tag for updating/reverting +- return False +- +- +-def select_link_function(self, tag): +- """Only customize if trying to leverage "attachments" in *GitHub* releases +- +- A way to select from one or multiple attached donwloadable files from the +- server, instead of downloading the default release/tag source code +- """ +- +- # -- Default, universal case (and is the only option for GitLab/Bitbucket) +- link = tag["zipball_url"] +- +- # -- Example: select the first (or only) asset instead source code -- +- #if "assets" in tag and "browser_download_url" in tag["assets"][0]: +- # link = tag["assets"][0]["browser_download_url"] +- +- # -- Example: select asset based on OS, where multiple builds exist -- +- # # not tested/no error checking, modify to fit your own needs! +- # # assume each release has three attached builds: +- # # release_windows.zip, release_OSX.zip, release_linux.zip +- # # This also would logically not be used with "branches" enabled +- # if platform.system() == "Darwin": # ie OSX +- # link = [asset for asset in tag["assets"] if 'OSX' in asset][0] +- # elif platform.system() == "Windows": +- # link = [asset for asset in tag["assets"] if 'windows' in asset][0] +- # elif platform.system() == "Linux": +- # link = [asset for asset in tag["assets"] if 'linux' in asset][0] +- +- return link +- +- +-# ----------------------------------------------------------------------------- +-# Register, should be run in the register module itself +-# ----------------------------------------------------------------------------- +- +- +-classes = ( +- addon_updater_install_popup, +- addon_updater_check_now, +- addon_updater_update_now, +- addon_updater_update_target, +- addon_updater_install_manually, +- addon_updater_updated_successful, +- addon_updater_restore_backup, +- addon_updater_ignore, +- addon_updater_end_background +-) +- +- +-def register(bl_info): +- """Registering the operators in this module""" +- # safer failure in case of issue loading module +- if updater.error: +- print("Exiting updater registration, " + updater.error) +- return +- updater.clear_state() # clear internal vars, avoids reloading oddities +- +- # confirm your updater "engine" (Github is default if not specified) +- updater.engine = "Github" +- # updater.engine = "GitLab" +- # updater.engine = "Bitbucket" +- +- # If using private repository, indicate the token here +- # Must be set after assigning the engine. +- # **WARNING** Depending on the engine, this token can act like a password!! +- # Only provide a token if the project is *non-public*, see readme for +- # other considerations and suggestions from a security standpoint +- updater.private_token = None # "tokenstring" +- +- # choose your own username, must match website (not needed for GitLab) +- updater.user = "gregzaal" +- +- # choose your own repository, must match git name +- updater.repo = "Gaffer" +- +- #updater.addon = # define at top of module, MUST be done first +- +- # Website for manual addon download, optional but recommended to set +- updater.website = "https://blendermarket.com/products/gaffer-light-manager" +- +- # Addon subfolder path +- # "sample/path/to/addon" +- # default is "" or None, meaning root +- updater.subfolder_path = "" +- +- # used to check/compare versions +- updater.current_version = bl_info["version"] +- +- # Optional, to hard-set update frequency, use this here - however, +- # this demo has this set via UI properties. +- # updater.set_check_interval( +- # enable=False,months=0,days=0,hours=0,minutes=2) +- +- # Optional, consider turning off for production or allow as an option +- # This will print out additional debugging info to the console +- updater.verbose = False # make False for production default +- +- # Optional, customize where the addon updater processing subfolder is, +- # essentially a staging folder used by the updater on its own +- # Needs to be within the same folder as the addon itself +- # Need to supply a full, absolute path to folder +- # updater.updater_path = # set path of updater folder, by default: +- # /addons/{__package__}/{__package__}_updater +- +- # auto create a backup of the addon when installing other versions +- updater.backup_current = True # True by default +- +- # Sample ignore patterns for when creating backup of current during update +- updater.backup_ignore_patterns = ["__pycache__"] +- # Alternate example patterns +- # updater.backup_ignore_patterns = [".git", "__pycache__", "*.bat", ".gitignore", "*.exe"] +- +- # Patterns for files to actively overwrite if found in new update +- # file and are also found in the currently installed addon. Note that +- +- # by default (ie if set to []), updates are installed in the same way as blender: +- # .py files are replaced, but other file types (e.g. json, txt, blend) +- # will NOT be overwritten if already present in current install. Thus +- # if you want to automatically update resources/non py files, add them +- # as a part of the pattern list below so they will always be overwritten by an +- # update. If a pattern file is not found in new update, no action is taken +- # This does NOT detele anything, only defines what is allowed to be overwritten +- updater.overwrite_patterns = ["*.png","*.jpg","README.md","LICENSE.txt"] +- # updater.overwrite_patterns = [] +- # other examples: +- # ["*"] means ALL files/folders will be overwritten by update, was the behavior pre updater v1.0.4 +- # [] or ["*.py","*.pyc"] matches default blender behavior, ie same effect if user installs update manually without deleting the existing addon first +- # e.g. if existing install and update both have a resource.blend file, the existing installed one will remain +- # ["some.py"] means if some.py is found in addon update, it will overwrite any existing some.py in current addon install, if any +- # ["*.json"] means all json files found in addon update will overwrite those of same name in current install +- # ["*.png","README.md","LICENSE.txt"] means the readme, license, and all pngs will be overwritten by update +- +- # Patterns for files to actively remove prior to running update +- # Useful if wanting to remove old code due to changes in filenames +- # that otherwise would accumulate. Note: this runs after taking +- # a backup (if enabled) but before placing in new update. If the same +- # file name removed exists in the update, then it acts as if pattern +- # is placed in the overwrite_patterns property. Note this is effectively +- # ignored if clean=True in the run_update method +- updater.remove_pre_update_patterns = ["*.py", "*.pyc"] +- # Note setting ["*"] here is equivalent to always running updates with +- # clean = True in the run_update method, ie the equivalent of a fresh, +- # new install. This would also delete any resources or user-made/modified +- # files setting ["__pycache__"] ensures the pycache folder is always removed +- # The configuration of ["*.py","*.pyc"] is a safe option as this +- # will ensure no old python files/caches remain in event different addon +- # versions have different filenames or structures +- +- # Allow branches like 'master' as an option to update to, regardless +- # of release or version. +- # Default behavior: releases will still be used for auto check (popup), +- # but the user has the option from user preferences to directly +- # update to the master branch or any other branches specified using +- # the "install {branch}/older version" operator. +- updater.include_branches = True +- +- # (GitHub only) This options allows the user to use releases over tags for data, +- # which enables pulling down release logs/notes, as well as specify installs from +- # release-attached zips (instead of just the auto-packaged code generated with +- # a release/tag). Setting has no impact on BitBucket or GitLab repos +- updater.use_releases = False +- # note: Releases always have a tag, but a tag may not always be a release +- # Therefore, setting True above will filter out any non-annoted tags +- # note 2: Using this option will also display the release name instead of +- # just the tag name, bear this in mind given the skip_tag_function filtering above +- +- # if using "include_branches", +- # updater.include_branch_list defaults to ['master'] branch if set to none +- # example targeting another multiple branches allowed to pull from +- # updater.include_branch_list = ['master', 'dev'] # example with two branches +- updater.include_branch_list = None # None is the equivalent to setting ['master'] +- +- # Only allow manual install, thus prompting the user to open +- # the addon's web page to download, specifically: updater.website +- # Useful if only wanting to get notification of updates but not +- # directly install. +- updater.manual_only = False +- +- # Used for development only, "pretend" to install an update to test +- # reloading conditions +- updater.fake_install = False # Set to true to test callback/reloading +- +- # Show popups, ie if auto-check for update is enabled or a previous +- # check for update in user preferences found a new version, show a popup +- # (at most once per blender session, and it provides an option to ignore +- # for future sessions); default behavior is set to True +- updater.showpopups = True +- # note: if set to false, there will still be an "update ready" box drawn +- # using the `update_notice_box_ui` panel function. +- +- # Override with a custom function on what tags +- # to skip showing for updater; see code for function above. +- # Set the min and max versions allowed to install. +- # Optional, default None +- # min install (>=) will install this and higher +- updater.version_min_update = None +- # updater.version_min_update = None # if not wanting to define a min +- +- # max install (<) will install strictly anything lower +- # updater.version_max_update = (9,9,9) +- updater.version_max_update = None # set to None if not wanting to set max +- +- # Function defined above, customize as appropriate per repository +- updater.skip_tag = skip_tag_function # min and max used in this function +- +- # Function defined above, customize as appropriate per repository; not required +- updater.select_link = select_link_function +- +- # The register line items for all operators/panels +- # If using bpy.utils.register_module(__name__) to register elsewhere +- # in the addon, delete these lines (also from unregister) +- for cls in classes: +- # apply annotations to remove Blender 2.8 warnings, no effect on 2.7 +- make_annotations(cls) +- # comment out this line if using bpy.utils.register_module(__name__) +- bpy.utils.register_class(cls) +- +- # special situation: we just updated the addon, show a popup +- # to tell the user it worked +- # should be enclosed in try/catch in case other issues arise +- showReloadPopup() +- +- +-def unregister(): +- for cls in reversed(classes): +- # comment out this line if using bpy.utils.unregister_module(__name__) +- bpy.utils.unregister_class(cls) +- +- # clear global vars since they may persist if not restarting blender +- updater.clear_state() # clear internal vars, avoids reloading oddities +- +- global ran_autocheck_install_popup +- ran_autocheck_install_popup = False +- +- global ran_update_sucess_popup +- ran_update_sucess_popup = False +- +- global ran_background_check +- ran_background_check = False diff --git a/ui.py b/ui.py -index c6a35bb..6394b67 100644 +index 2809021..3c2291c 100644 --- a/ui.py +++ b/ui.py @@ -17,7 +17,6 @@ @@ -87,21 +3205,21 @@ index c6a35bb..6394b67 100644 import bpy -from . import addon_updater_ops from collections import OrderedDict - import bgl, blf - from math import pi, cos, sin, log -@@ -664,7 +663,6 @@ class GafferPanelLights(bpy.types.Panel): - return True if context.scene.render.engine in supported_renderers else False + import bgl + import blf +@@ -582,7 +581,6 @@ class GAFFER_PT_lights(bpy.types.Panel): + bl_category = "Gaffer" def draw(self, context): -- addon_updater_ops.check_for_update_background(context) +- addon_updater_ops.check_for_update_background() scene = context.scene gaf_props = scene.gaf_props -@@ -704,7 +702,6 @@ class GafferPanelLights(bpy.types.Panel): - else: - layout.label ("Render Engine not supported!") +@@ -646,7 +644,6 @@ class GAFFER_PT_lights(bpy.types.Panel): + text="", + icon='URL').url = "https://forms.gle/R22DphecWsXmaLAr9" - addon_updater_ops.update_notice_box_ui(self, context) - class GafferPanelTools(bpy.types.Panel): + class GAFFER_PT_tools(bpy.types.Panel): |