Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'uv_magic_uv/addon_updater_ops.py')
-rw-r--r--uv_magic_uv/addon_updater_ops.py1357
1 files changed, 1357 insertions, 0 deletions
diff --git a/uv_magic_uv/addon_updater_ops.py b/uv_magic_uv/addon_updater_ops.py
new file mode 100644
index 00000000..418334ad
--- /dev/null
+++ b/uv_magic_uv/addon_updater_ops.py
@@ -0,0 +1,1357 @@
+# ##### 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 bpy
+from bpy.app.handlers import persistent
+import os
+
+# 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 = "magic_uv"
+
+dispaly_addon_name = "Magic UV"
+
+# -----------------------------------------------------------------------------
+# 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("Updater module error")
+ return
+ elif updater.update_ready == True:
+ col = layout.column()
+ col.scale_y = 0.7
+ col.label("Update {} ready!".format(str(updater.update_version)),
+ icon="LOOP_FORWARDS")
+ col.label("Choose 'Update Now' & press OK to install, ",icon="BLANK1")
+ col.label("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("No updates available")
+ col.label("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("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 "+str(res)+", error occurred")
+ 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')
+ 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 "+dispaly_addon_name+" 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):
+
+ # in case of error importing updater
+ 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 = context.user_preferences.addons[__package__].preferences
+ 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+" 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("Updater error")
+ return
+ split = layout.split(percentage=0.66)
+ subcol = split.column()
+ subcol.label("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 updater.verbose:
+ if res==0: print("Updater returned successful")
+ else: 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("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("There was an issue trying to auto-install",icon="ERROR")
+ col.label("Press the download button below and install",icon="BLANK1")
+ col.label("the zip file like a normal addon.",icon="BLANK1")
+ else:
+ col = layout.column()
+ col.scale_y = 0.7
+ col.label("Install the addon manually")
+ col.label("Press the download button below and install")
+ col.label("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("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("Updater error")
+ return
+
+ saved = updater.json
+ if self.error != "":
+ col = layout.column()
+ col.scale_y = 0.7
+ col.label("Error occurred, did not install", icon="ERROR")
+ col.label(updater.error_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("Addon restored", icon="RECOVER_LAST")
+ col.label("Restart blender to reload.",icon="BLANK1")
+ updater.json_reset_restore()
+ else:
+ col = layout.column()
+ col.scale_y = 0.7
+ col.label("Addon successfully installed", icon="FILE_TICK")
+ col.label("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("Addon restored", icon="RECOVER_LAST")
+ col.label("Consider restarting blender to fully reload.",icon="BLANK1")
+ updater.json_reset_restore()
+ else:
+ col = layout.column()
+ col.scale_y = 0.7
+ col.label("Addon successfully installed", icon="FILE_TICK")
+ col.label("Consider restarting blender to fully reload.", icon="BLANK1")
+
+ def execut(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')
+
+
+# passed into the updater, background thread updater
+def background_update_callback(update_ready):
+ 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
+
+
+# a callback for once the updater has completed
+# Only makes sense to use this if "auto_reload_post_update" == False,
+# i.e. don't auto-restart the addon
+def post_update_callback(res=None):
+
+ # 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 occured 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()
+
+# function for asynchronous background check, which *could* be called on register
+def check_for_update_background():
+
+ # in case of error importing updater
+ 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
+ addon_prefs = bpy.context.user_preferences.addons.get(__package__, None)
+ if not addon_prefs:
+ return
+ settings = addon_prefs.preferences
+ 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
+
+
+# can be placed in front of other operators to launch when pressed
+def check_for_update_nonthreaded(self, context):
+
+ # in case of error importing updater
+ 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 = context.user_preferences.addons[__package__].preferences
+ 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")
+
+# for use in register only, to show popup after re-enabling the addon
+# must be enabled by developer
+def showReloadPopup():
+
+ # in case of error importing updater
+ 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
+# -----------------------------------------------------------------------------
+
+
+# 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
+def update_notice_box_ui(self, context):
+
+ # in case of error importing updater
+ 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("Restart blender", icon="ERROR")
+ col.label("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
+
+ settings = context.user_preferences.addons[__package__].preferences
+ layout = self.layout
+ box = layout.box()
+ col = box.column(align=True)
+ col.label("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,
+ "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, "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
+
+
+# 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)
+def update_settings_ui(self, context, element=None):
+ # 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("Error initializing updater code:")
+ box.label(updater.error_msg)
+ return
+
+ settings = context.user_preferences.addons[__package__].preferences
+
+ # auto-update settings
+ box.label("Updater Settings")
+ 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("Restart blender to complete update", icon="ERROR")
+ return
+
+ split = row.split(percentage=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("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,
+ updater.error)
+ else:
+ split.enabled = False
+ split.operator(addon_updater_check_now.bl_idname,
+ 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,
+ "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,
+ "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,
+ "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",
+ "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,
+ "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,
+ "Install latest {} / old version".format(branch))
+ else:
+ col.operator(addon_updater_update_target.bl_idname,
+ "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, 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(updater.error_msg)
+ elif lastcheck != "" and lastcheck != None:
+ lastcheck = lastcheck[0: lastcheck.index(".") ]
+ row.label("Last update check: " + lastcheck)
+ else:
+ row.label("Last update check: Never")
+
+
+# Preferences - Condensed drawing within preferences
+# alternate draw for user preferences or other places, does not draw a box
+def update_settings_ui_condensed(self, context, element=None):
+ # 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("Error initializing updater code:")
+ row.label(updater.error_msg)
+ return
+
+ settings = context.user_preferences.addons[__package__].preferences
+
+ # 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("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,
+ updater.error)
+ else:
+ split.enabled = False
+ split.operator(addon_updater_check_now.bl_idname,
+ 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,
+ "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,
+ "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,
+ "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",
+ "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,
+ "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(updater.error_msg)
+ elif lastcheck != "" and lastcheck != None:
+ lastcheck = lastcheck[0: lastcheck.index(".") ]
+ row.label("Last check: " + lastcheck)
+ else:
+ row.label("Last check: Never")
+
+
+# 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
+def skip_tag_function(self, tag):
+
+ # 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
+
+# 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
+def select_link_function(self, tag):
+ link = ""
+
+ # -- 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
+# -----------------------------------------------------------------------------
+
+
+# registering the operators in this module
+def register(bl_info):
+
+ # See output to verify this register function is working properly
+ # print("Running updater reg")
+
+ # safer failure in case of issue loading module
+ if updater.error != None:
+ print("Exiting updater registration, error return")
+ return
+
+ # 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 = "nutti"
+
+ # choose your own repository, must match git name
+ updater.repo = "Magic-UV"
+
+ #updater.addon = # define at top of module, MUST be done first
+
+ # Website for manual addon download, optional but recommended to set
+ updater.website = "https://github.com/nutti/Magic-UV"
+
+ # Addon subfolder path
+ # "sample/path/to/addon"
+ # default is "" or None, meaning root
+ updater.subfolder_path = "uv_magic_uv"
+
+ # 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 = ['master', 'develop'] # 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 = (5,2,0)
+ # 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 # if not wanting to define a 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)
+ bpy.utils.register_class(addon_updater_install_popup)
+ bpy.utils.register_class(addon_updater_check_now)
+ bpy.utils.register_class(addon_updater_update_now)
+ bpy.utils.register_class(addon_updater_update_target)
+ bpy.utils.register_class(addon_updater_install_manually)
+ bpy.utils.register_class(addon_updater_updated_successful)
+ bpy.utils.register_class(addon_updater_restore_backup)
+ bpy.utils.register_class(addon_updater_ignore)
+ bpy.utils.register_class(addon_updater_end_background)
+
+ # 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():
+ bpy.utils.unregister_class(addon_updater_install_popup)
+ bpy.utils.unregister_class(addon_updater_check_now)
+ bpy.utils.unregister_class(addon_updater_update_now)
+ bpy.utils.unregister_class(addon_updater_update_target)
+ bpy.utils.unregister_class(addon_updater_install_manually)
+ bpy.utils.unregister_class(addon_updater_updated_successful)
+ bpy.utils.unregister_class(addon_updater_restore_backup)
+ bpy.utils.unregister_class(addon_updater_ignore)
+ bpy.utils.unregister_class(addon_updater_end_background)
+
+ # 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