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:
@@ -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",
|
||||||
|
@@ -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,11 +111,11 @@ 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):
|
||||||
@@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user