# ====================== 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 3 # 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, see . # # ======================= END GPL LICENSE BLOCK ======================== import bpy import addon_utils import json import urllib.request import logging import shutil from bpy.props import StringProperty logging.basicConfig(format='%(asctime)-15s %(levelname)8s %(name)s %(message)s', level=logging.INFO) log = logging.getLogger('networking') class WM_OT_update_index(bpy.types.Operator): """Check for updated list of add-ons available for download""" bl_idname = "wm.update_index" bl_label = "Check for updated list of add-ons" def execute(self, context): # Download the index.json file try: req = urllib.request.urlopen("https://git.blender.org/gitweb/gitweb.cgi/" "blender-package-manager-addon.git/blob_plain/HEAD:/addons/index.json") index_file = req.read().decode('utf-8') req.close() except urllib.error.HTTPError as err: self.report({'ERROR'}, "Error requesting update: " + str(err.code) + " " + err.reason) return {'CANCELLED'} # Parse downloaded file try: addon_list = json.loads(index_file) except ValueError as err: self.report({'ERROR'}, "Error: JSON file could not parse.") log.warning("ValueError: %s", err) return {'CANCELLED'} # Get the add-on preferences prefs = bpy.context.user_preferences.addons.get("package_manager").preferences # Clear previous list of add-ons prefs.pm_addons.clear() prefs.pm_addons_index = 0 user_path = bpy.utils.user_resource('SCRIPTS', path="addons") installed = addon_utils.modules() # Loop through every add-on in the parsed json for name, content in addon_list["addons"].items(): # Skip add-ons not installed to USER directory # TODO: support above later for a in installed: if name == a.__name__ and user_path not in a.__file__: log.warning("Not listing add-on %s, as it is installed to " "location other than USER path", name) break else: # Add new add-on to the list addon = prefs.pm_addons.add() self.load_addon_data(addon, name, content) return {'FINISHED'} def load_addon_data(self, addon, module_name, content): addon.name = content["name"] addon.blender = '.'.join(map(str, content["blender"])) addon.module_name = module_name if "author" in content: addon.author = content["author"] if "category" in content: addon.category = content["category"] if "description" in content: addon.description = content["description"] if "filename" in content: addon.filename = content["filename"] if "location" in content: addon.location = content["location"] if "source" in content: addon.source = content["source"] if "support" in content: addon.support = content["support"] if "tracker_url" in content: addon.tracker_url = content["tracker_url"] if "version" in content: # TODO: add multi-version functionality addon.version = '.'.join(map(str, content["version"])) if "wiki_url" in content: addon.wiki_url = content["wiki_url"] if "warning" in content: addon.warning = content["warning"] class WM_OT_addon_download_install(bpy.types.Operator): """Download and install add-on""" bl_idname = "wm.addon_download_install" bl_label = "Download and install selected add-on" addon = bpy.props.StringProperty() def execute(self, context): if self.addon is None: return {'CANCELLED'} # Get the add-on preferences prefs = bpy.context.user_preferences.addons.get("package_manager").preferences for addon in prefs.pm_addons: if addon.module_name == self.addon: break else: print("failed :(") return {'CANCELLED'} # TODO: specify filetype if self.download(self.addon): if self.install(self.addon): return {'FINISHED'} return {'CANCELLED'} def download(self, addon, filetype=".py"): filename = addon + filetype download_path = bpy.utils.user_resource('SCRIPTS', path="addons/package_manager/download%s" % filetype) try: req = urllib.request.urlopen("http://localhost:8000/%s" % filename) with open(download_path, 'wb') as download_file: shutil.copyfileobj(req, download_file) req.close() except urllib.error.HTTPError as err: log.warning("Download failed with HTTPError: %s %s", str(err.code), err.reason) return False return True def install(self, addon, filetype=".py"): filename = addon + filetype if filetype == ".py" else "" download_path = bpy.utils.user_resource('SCRIPTS', path="addons/package_manager/download%s" % filetype) addon_path = bpy.utils.user_resource('SCRIPTS', path="addons/%s" % filename) if filetype == ".py": shutil.move(download_path, addon_path) return True def register(): bpy.utils.register_class(WM_OT_update_index) bpy.utils.register_class(WM_OT_addon_download_install) def unregister(): bpy.utils.unregister_class(WM_OT_update_index) bpy.utils.unregister_class(WM_OT_addon_download_install)