Adding support for download and install of .zip addons

Minor UI alteration, and some small changes based on Sybren's feedback
This commit is contained in:
2016-06-24 13:54:27 -05:00
parent 8c87eeee85
commit b2c34a1a7e
2 changed files with 47 additions and 37 deletions

View File

@@ -56,8 +56,8 @@ class PackageManagerAddon(bpy.types.PropertyGroup):
blender = StringProperty() blender = StringProperty()
warning = StringProperty() warning = StringProperty()
support = StringProperty() support = StringProperty()
filename = StringProperty()
module_name = StringProperty() module_name = StringProperty()
download_url = StringProperty()
class PackageManagerPreferences(AddonPreferences): class PackageManagerPreferences(AddonPreferences):
# this must match the addon name, use '__package__' # this must match the addon name, use '__package__'
@@ -81,18 +81,17 @@ class PackageManagerPreferences(AddonPreferences):
# Display selected add-on # Display selected add-on
addon = self.pm_addons[self.pm_addons_index] addon = self.pm_addons[self.pm_addons_index]
installed = False installed = any(module.__name__ == addon.module_name for module in addon_utils.modules())
for module in addon_utils.modules():
if module.__name__ == addon.module_name:
installed = True
break
col_box = layout.column() col_box = layout.column()
box = col_box.box() box = col_box.box()
colsub = box.column() colsub = box.column()
split = colsub.row().split(percentage=0.25) split = colsub.row().split(percentage=0.25)
split.label(text="Installed: %s" % ("Yes" if installed else "No")) if installed and addon.version is not None:
split.label(text="Installed: Yes, v%s" % addon.version)
else:
split.label(text="Installed: No")
split.separator() split.separator()
split.separator() split.separator()
split.operator("wm.addon_download_install", split.operator("wm.addon_download_install",

View File

@@ -18,15 +18,18 @@
import bpy import bpy
import addon_utils import addon_utils
import json import json
import urllib.request
import logging import logging
import os
import shutil import shutil
import urllib.request
import zipfile
from bpy.props import StringProperty from bpy.props import StringProperty
logging.basicConfig(format='%(asctime)-15s %(levelname)8s %(name)s %(message)s',
level=logging.INFO)
log = logging.getLogger('networking') log = logging.getLogger('networking')
INDEX_DOWNLOAD_URL = ("https://git.blender.org/gitweb/gitweb.cgi/"
"blender-package-manager-addon.git/blob_plain/HEAD:/addons/index.json")
class WM_OT_update_index(bpy.types.Operator): class WM_OT_update_index(bpy.types.Operator):
"""Check for updated list of add-ons available for download""" """Check for updated list of add-ons available for download"""
bl_idname = "wm.update_index" bl_idname = "wm.update_index"
@@ -35,13 +38,11 @@ class WM_OT_update_index(bpy.types.Operator):
def execute(self, context): def execute(self, context):
# Download the index.json file # Download the index.json file
try: try:
req = urllib.request.urlopen("https://git.blender.org/gitweb/gitweb.cgi/" req = urllib.request.urlopen(INDEX_DOWNLOAD_URL)
"blender-package-manager-addon.git/blob_plain/HEAD:/addons/index.json")
index_file = req.read().decode('utf-8') index_file = req.read().decode('utf-8')
req.close() req.close()
except urllib.error.HTTPError as err: except urllib.error.HTTPError as err:
self.report({'ERROR'}, "Error requesting update: " self.report({'ERROR'}, "Error requesting update: %s %s" % (str(err.code), err.reason))
+ str(err.code) + " " + err.reason)
return {'CANCELLED'} return {'CANCELLED'}
# Parse downloaded file # Parse downloaded file
@@ -64,11 +65,11 @@ class WM_OT_update_index(bpy.types.Operator):
# Loop through every add-on in the parsed json # Loop through every add-on in the parsed json
for name, content in addon_list["addons"].items(): for name, content in addon_list["addons"].items():
# Skip add-ons not installed to USER directory # Skip add-ons not installed to USER path
# TODO: support above later # TODO: support above later
for a in installed: for a in installed:
if name == a.__name__ and user_path not in a.__file__: if name == a.__name__ and user_path not in a.__file__:
log.warning("Not listing add-on %s, as it is installed to " log.info("Not listing add-on %s, as it is installed to "
"location other than USER path", name) "location other than USER path", name)
break break
else: else:
@@ -83,6 +84,7 @@ class WM_OT_update_index(bpy.types.Operator):
addon.name = content["name"] addon.name = content["name"]
addon.blender = '.'.join(map(str, content["blender"])) addon.blender = '.'.join(map(str, content["blender"]))
addon.module_name = module_name addon.module_name = module_name
addon.download_url = content["download_url"]
if "author" in content: if "author" in content:
addon.author = content["author"] addon.author = content["author"]
@@ -93,9 +95,6 @@ class WM_OT_update_index(bpy.types.Operator):
if "description" in content: if "description" in content:
addon.description = content["description"] addon.description = content["description"]
if "filename" in content:
addon.filename = content["filename"]
if "location" in content: if "location" in content:
addon.location = content["location"] addon.location = content["location"]
@@ -112,12 +111,12 @@ class WM_OT_update_index(bpy.types.Operator):
# TODO: add multi-version functionality # TODO: add multi-version functionality
addon.version = '.'.join(map(str, content["version"])) addon.version = '.'.join(map(str, content["version"]))
if "wiki_url" in content:
addon.wiki_url = content["wiki_url"]
if "warning" in content: if "warning" in content:
addon.warning = content["warning"] addon.warning = content["warning"]
if "wiki_url" in content:
addon.wiki_url = content["wiki_url"]
class WM_OT_addon_download_install(bpy.types.Operator): class WM_OT_addon_download_install(bpy.types.Operator):
"""Download and install add-on""" """Download and install add-on"""
@@ -132,48 +131,60 @@ class WM_OT_addon_download_install(bpy.types.Operator):
# Get the add-on preferences # Get the add-on preferences
prefs = bpy.context.user_preferences.addons.get("package_manager").preferences prefs = bpy.context.user_preferences.addons.get("package_manager").preferences
# Verify add-on is in list and find its download url
download_url = ""
for addon in prefs.pm_addons: for addon in prefs.pm_addons:
if addon.module_name == self.addon: if addon.module_name == self.addon:
download_url = addon.download_url
break break
else: else:
print("failed :(")
return {'CANCELLED'} return {'CANCELLED'}
# TODO: specify filetype ext = os.path.splitext(download_url)[1]
if self.download(self.addon): # Download and install the selected add-on
if self.install(self.addon): if self.download(self.addon, download_url):
if self.install(self.addon, ext):
return {'FINISHED'} return {'FINISHED'}
return {'CANCELLED'} return {'CANCELLED'}
def download(self, addon, filetype=".py"): def download(self, addon, download_url):
filename = addon + filetype filetype = os.path.splitext(download_url)[1]
download_path = bpy.utils.user_resource('SCRIPTS', download_path = bpy.utils.user_resource('SCRIPTS', path="addons/package_manager/"
path="addons/package_manager/download%s" % filetype) "download%s" % filetype)
# Download add-on and save to disk
try: try:
req = urllib.request.urlopen("http://localhost:8000/%s" % filename) req = urllib.request.urlopen(download_url)
with open(download_path, 'wb') as download_file: with open(download_path, 'wb') as download_file:
shutil.copyfileobj(req, download_file) shutil.copyfileobj(req, download_file)
req.close() req.close()
except urllib.error.HTTPError as err: except urllib.error.HTTPError as err:
log.warning("Download failed with HTTPError: %s %s", log.warning("Download failed with HTTPError: %s %s", str(err.code), err.reason)
str(err.code), err.reason)
return False return False
return True return True
def install(self, addon, filetype=".py"): def install(self, addon, filetype):
filename = addon + filetype if filetype == ".py" else "" filename = addon + (filetype if filetype == ".py" else "")
download_path = bpy.utils.user_resource('SCRIPTS', download_path = bpy.utils.user_resource('SCRIPTS', path="addons/package_manager/"
path="addons/package_manager/download%s" % filetype) "download%s" % filetype)
addon_path = bpy.utils.user_resource('SCRIPTS', path="addons/%s" % filename) addon_path = bpy.utils.user_resource('SCRIPTS', path="addons/%s" % filename)
# Copy downloaded add-on to USER scripts path
if filetype == ".py": if filetype == ".py":
shutil.move(download_path, addon_path) shutil.move(download_path, addon_path)
elif filetype == ".zip":
# Remove existing add-on
if os.path.exists(addon_path):
shutil.rmtree(addon_path)
with zipfile.ZipFile(download_path,"r") as zipped_addon:
zipped_addon.extractall(bpy.utils.user_resource('SCRIPTS', path="addons"))
return True return True