summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorbartus2019-05-14 21:38:23 +0200
committerbartus2019-05-14 21:38:23 +0200
commitdc69d64178ed68b66c78031ccbd24a2b3a081e8b (patch)
treecf9db880c92ef80871cf257584690c405a0eb5e4
parentf87e9bc507363372055bfd1b6beb21e5aa45fd72 (diff)
downloadaur-dc69d64178ed68b66c78031ccbd24a2b3a081e8b.tar.gz
update to v3.1.2
-rw-r--r--.SRCINFO6
-rw-r--r--PKGBUILD8
-rw-r--r--remove.updater.patch3204
3 files changed, 3168 insertions, 50 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 92b4800f5c14..b342251eadc3 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -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
diff --git a/PKGBUILD b/PKGBUILD
index f3c4142efadf..7c04361933f7 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -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):