diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 1e239f6f25..f6e046228c 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -38,7 +38,6 @@ installed. ''' import os import re -import re import shutil import stat import sys @@ -50,29 +49,15 @@ if sys.version_info.major < 3: import urllib2 else: import urllib.request as urllib2 -ctx = None -try: - import ssl -except: - pass -else: - if hasattr(ssl,"create_default_context"): - ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + +from addonmanager_macro import Macro +from addonmanager_utilities import translate +from addonmanager_utilities import urlopen NOGIT = False # for debugging purposes, set this to True to always use http downloads MACROS_BLACKLIST = ["BOLTS","WorkFeatures","how to install","PartsLibrary","FCGear"] - -# Qt tanslation handling -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig) - if sys.version_info.major < 3: import StringIO as io _stringio = io.StringIO @@ -102,16 +87,6 @@ def symlink(source, link_name): raise ctypes.WinError() -def get_macro_dir(): - """Return the directory where macros are located""" - default_macro_dir = os.path.join(FreeCAD.ConfigGet('UserAppData'), 'Macro') - path = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString('MacroPath', default_macro_dir) - # For Py2 path is a utf-8 encoded unicode which we must decode again - if sys.version_info.major < 3: - path = path.decode("utf-8") - return path - - def update_macro_details(old_macro, new_macro): """Update a macro with information from another one @@ -356,7 +331,7 @@ class AddonsInstaller(QtGui.QDialog): self.install_worker.start() elif self.tabWidget.currentIndex() == 1: # Tab "Macros". - macro_dir = get_macro_dir() + macro_dir = FreeCAD.getUserMacroDir() if not os.path.isdir(macro_dir): os.makedirs(macro_dir) macro = self.macros[self.listMacros.currentRow()] @@ -439,8 +414,8 @@ class AddonsInstaller(QtGui.QDialog): if not macro.is_installed(): # Macro not installed, nothing to do. return - macro_path = os.path.join(get_macro_dir(), macro.filename) - macro_path_with_macro_prefix = os.path.join(get_macro_dir(), 'Macro_' + macro.filename) + macro_path = os.path.join(FreeCAD.getUserMacroDir(), macro.filename) + macro_path_with_macro_prefix = os.path.join(FreeCAD.getUserMacroDir(), 'Macro_' + macro.filename) if os.path.exists(macro_path): os.remove(macro_path) self.labelDescription.setText(translate("AddonsInstaller", "Macro successfully removed.")) @@ -465,7 +440,6 @@ class AddonsInstaller(QtGui.QDialog): self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme('dialog-ok'), macro.name + str(' (Installed)'))) else: self.listMacros.addItem(macro.name) - self.listMacros.sortItems() def mark(self,repo): for i in range(self.listWorkbenches.count()): @@ -488,10 +462,7 @@ class UpdateWorker(QtCore.QThread): def run(self): "populates the list of addons" self.progressbar_show.emit(True) - if ctx: - u = urllib2.urlopen("https://github.com/FreeCAD/FreeCAD-addons",context=ctx) - else: - u = urllib2.urlopen("https://github.com/FreeCAD/FreeCAD-addons") + u = urlopen("https://github.com/FreeCAD/FreeCAD-addons") p = u.read() if sys.version_info.major >= 3 and isinstance(p, bytes): p = p.decode("utf-8") @@ -535,10 +506,7 @@ class InfoWorker(QtCore.QThread): i = 0 for repo in self.repos: url = repo[1] - if ctx: - u = urllib2.urlopen(url,context=ctx) - else: - u = urllib2.urlopen(url) + u = urlopen(url) p = u.read() if sys.version_info.major >= 3 and isinstance(p, bytes): p = p.decode("utf-8") @@ -611,134 +579,6 @@ class CheckWBWorker(QtCore.QThread): self.stop = True -class Macro(object): - def __init__(self, name): - self.name = name - self.on_wiki = False - self.on_git = False - self.desc = '' - self.code = '' - self.url = '' - self.version = '' - self.src_filename = '' - self.parsed = False - - def __eq__(self, other): - return self.filename == other.filename - - @property - def filename(self): - if self.on_git: - return os.path.basename(self.src_filename) - return (self.name + '.FCMacro').replace(' ', '_') - - def is_installed(self): - if self.on_git and not self.src_filename: - return False - return (os.path.exists(os.path.join(get_macro_dir(), self.filename)) - or os.path.exists(os.path.join(get_macro_dir(), 'Macro_' + self.filename))) - - def fill_details_from_file(self, filename): - with open(filename) as f: - number_of_required_fields = 3 # Fields __Comment__, __Web__, __Version__. - re_desc = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1") - re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1") - re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1") - for l in f.readlines(): - match = re.match(re_desc, l) - if match: - self.desc = match.group(2) - number_of_required_fields -= 1 - match = re.match(re_url, l) - if match: - self.url = match.group(2) - number_of_required_fields -= 1 - match = re.match(re_version, l) - if match: - self.version = match.group(2) - number_of_required_fields -= 1 - if number_of_required_fields <= 0: - break - f.seek(0) - self.code = f.read() - self.parsed = True - - def fill_details_from_wiki(self, url): - code = "" - try: - if ctx: - u = urllib2.urlopen(url, context=ctx) - else: - u = urllib2.urlopen(url) - except urllib2.HTTPError: - return - p = u.read() - if sys.version_info.major >= 3 and isinstance(p, bytes): - p = p.decode('utf-8') - u.close() - # check if the macro page has its code hosted elsewhere, download if needed - if "rawcodeurl" in p: - rawcodeurl = re.findall("rawcodeurl.*?href=\"(http.*?)\">",p) - if rawcodeurl: - rawcodeurl = rawcodeurl[0] - try: - if ctx: - u2 = urllib2.urlopen(rawcodeurl, context=ctx) - else: - u2 = urllib2.urlopen(rawcodeurl) - except urllib2.HTTPError: - return - # code = u2.read() - # github is slow to respond... We need to use this trick below - response = "" - block = 8192 - #expected = int(u2.headers['content-length']) - while 1: - #print("expected:",expected,"got:",len(response)) - data = u2.read(block) - if not data: - break - response += data - if response: - code = response - if sys.version_info.major >= 3 and isinstance(code, bytes): - code = code.decode('utf-8') - u2.close() - if not code: - code = re.findall('
(.*?)<\/pre>', p.replace('\n', '--endl--'))
- if code:
- # code = code[0]
- # take the biggest code block
- code = sorted(code, key=len)[-1]
- code = code.replace('--endl--', '\n')
- else:
- FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Unable to fetch the code of this macro."))
- # Clean HTML escape codes.
- try:
- from HTMLParser import HTMLParser
- except ImportError:
- from html.parser import HTMLParser
- if sys.version_info.major < 3:
- code = code.decode('utf8')
- try:
- code = HTMLParser().unescape(code)
- code = code.replace(b'\xc2\xa0'.decode("utf-8"), ' ')
- except:
- FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Unable to clean macro code: ") + code + '\n')
- if sys.version_info.major < 3:
- code = code.encode('utf8')
- desc = re.findall("(.*?)<\/td>", p.replace('\n', ' '))
- if desc:
- desc = desc[0]
- else:
- FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Unable to retrieve a description for this macro."))
- desc = "No description available"
- self.desc = desc
- self.url = url
- self.code = code
- self.parsed = True
-
-
class FillMacroListWorker(QtCore.QThread):
"""Populates the list of macros
"""
@@ -756,7 +596,8 @@ class FillMacroListWorker(QtCore.QThread):
self.retrieve_macros_from_git()
self.retrieve_macros_from_wiki()
[self.add_macro_signal.emit(m) for m in sorted(self.macros, key=lambda m: m.name.lower())]
- self.info_label_signal.emit(translate("AddonsInstaller", "List of macros successfully retrieved."))
+ if self.macros:
+ self.info_label_signal.emit(translate('AddonsInstaller', 'List of macros successfully retrieved.'))
self.progressbar_show.emit(False)
self.stop = True
@@ -770,9 +611,10 @@ class FillMacroListWorker(QtCore.QThread):
import git
except ImportError:
self.info_label_signal.emit("GitPython not installed! Cannot retrieve macros from git")
+ FreeCAD.Console.PrintWarning('GitPython not installed! Cannot retrieve macros from git')
return
- self.info_label_signal.emit("Downloading list of macros for git...")
+ self.info_label_signal.emit('Downloading list of macros for git...')
git.Repo.clone_from('https://github.com/FreeCAD/FreeCAD-macros.git', self.repo_dir)
for dirpath, _, filenames in os.walk(self.repo_dir):
if '.git' in dirpath:
@@ -784,7 +626,6 @@ class FillMacroListWorker(QtCore.QThread):
macro.src_filename = os.path.join(dirpath, filename)
self.macros.append(macro)
-
def retrieve_macros_from_wiki(self):
"""Retrieve macros from the wiki
@@ -793,15 +634,12 @@ class FillMacroListWorker(QtCore.QThread):
"""
self.info_label_signal.emit("Downloading list of macros...")
self.progressbar_show.emit(True)
- if ctx:
- u = urllib2.urlopen("https://www.freecadweb.org/wiki/Macros_recipes",context=ctx)
- else:
- u = urllib2.urlopen("https://www.freecadweb.org/wiki/Macros_recipes")
+ u = urlopen("https://www.freecadweb.org/wiki/Macros_recipes")
p = u.read()
+ u.close()
if sys.version_info.major >= 3 and isinstance(p, bytes):
p = p.decode("utf-8")
- u.close()
- macros = re.findall("title=\"(Macro.*?)\"",p)
+ macros = re.findall('title="(Macro.*?)"', p)
macros = [mac for mac in macros if ('translated' not in mac)]
for mac in macros:
macname = mac[6:] # Remove "Macro ".
@@ -831,10 +669,7 @@ class ShowWorker(QtCore.QThread):
else:
url = self.repos[self.idx][1]
self.info_label.emit(translate("AddonsInstaller", "Retrieving info from ") + str(url))
- if ctx:
- u = urllib2.urlopen(url,context=ctx)
- else:
- u = urllib2.urlopen(url)
+ u = urlopen(url)
p = u.read()
if sys.version_info.major >= 3 and isinstance(p, bytes):
p = p.decode("utf-8")
@@ -1036,10 +871,7 @@ class InstallWorker(QtCore.QThread):
depsurl += "/"
depsurl += "master/metadata.txt"
try:
- if ctx:
- mu = urllib2.urlopen(depsurl,context=ctx)
- else:
- mu = urllib2.urlopen(depsurl)
+ mu = urlopen(depsurl)
except urllib2.HTTPError:
# no metadata.txt, we just continue without deps checking
pass
@@ -1047,13 +879,13 @@ class InstallWorker(QtCore.QThread):
# metadata.txt found
depsfile = mu.read()
mu.close()
-
+
# urllib2 gives us a bytelike object instead of a string. Have to consider that
try:
depsfile = depsfile.decode('utf-8')
except AttributeError:
pass
-
+
deps = depsfile.split("\n")
for l in deps:
if l.startswith("workbenches="):
@@ -1099,10 +931,7 @@ class InstallWorker(QtCore.QThread):
zipurl = giturl+"/archive/master.zip"
try:
print("Downloading "+zipurl)
- if ctx:
- u = urllib2.urlopen(zipurl,context=ctx)
- else:
- u = urllib2.urlopen(zipurl)
+ u = urlopen(zipurl)
except:
return translate("AddonsInstaller", "Error: Unable to download") + " " + zipurl
zfile = _stringio()
diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py
new file mode 100644
index 0000000000..f002a69a09
--- /dev/null
+++ b/src/Mod/AddonManager/addonmanager_macro.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import sys
+
+from freecad import app
+
+from addonmanager_utilities import translate
+from addonmanager_utilities import urllib2
+from addonmanager_utilities import urlopen
+
+
+class Macro(object):
+ def __init__(self, name):
+ self.name = name
+ self.on_wiki = False
+ self.on_git = False
+ self.desc = ''
+ self.code = ''
+ self.url = ''
+ self.version = ''
+ self.src_filename = ''
+ self.parsed = False
+
+ def __eq__(self, other):
+ return self.filename == other.filename
+
+ @property
+ def filename(self):
+ if self.on_git:
+ return os.path.basename(self.src_filename)
+ return (self.name + '.FCMacro').replace(' ', '_')
+
+ def is_installed(self):
+ if self.on_git and not self.src_filename:
+ return False
+ return (os.path.exists(os.path.join(app.getUserMacroDir(), self.filename))
+ or os.path.exists(os.path.join(app.getUserMacroDir(), 'Macro_' + self.filename)))
+
+ def fill_details_from_file(self, filename):
+ with open(filename) as f:
+ number_of_required_fields = 3 # Fields __Comment__, __Web__, __Version__.
+ re_desc = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1")
+ re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1")
+ re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1")
+ for l in f.readlines():
+ match = re.match(re_desc, l)
+ if match:
+ self.desc = match.group(2)
+ number_of_required_fields -= 1
+ match = re.match(re_url, l)
+ if match:
+ self.url = match.group(2)
+ number_of_required_fields -= 1
+ match = re.match(re_version, l)
+ if match:
+ self.version = match.group(2)
+ number_of_required_fields -= 1
+ if number_of_required_fields <= 0:
+ break
+ f.seek(0)
+ self.code = f.read()
+ self.parsed = True
+
+ def fill_details_from_wiki(self, url):
+ code = ""
+ try:
+ u = urlopen(url)
+ except urllib2.HTTPError:
+ return
+ p = u.read()
+ if sys.version_info.major >= 3 and isinstance(p, bytes):
+ p = p.decode('utf-8')
+ u.close()
+ # check if the macro page has its code hosted elsewhere, download if needed
+ if "rawcodeurl" in p:
+ rawcodeurl = re.findall("rawcodeurl.*?href=\"(http.*?)\">",p)
+ if rawcodeurl:
+ rawcodeurl = rawcodeurl[0]
+ try:
+ u2 = urlopen(rawcodeurl)
+ except urllib2.HTTPError:
+ return
+ # code = u2.read()
+ # github is slow to respond... We need to use this trick below
+ response = ""
+ block = 8192
+ #expected = int(u2.headers['content-length'])
+ while 1:
+ #print("expected:",expected,"got:",len(response))
+ data = u2.read(block)
+ if not data:
+ break
+ response += data
+ if response:
+ code = response
+ if sys.version_info.major >= 3 and isinstance(code, bytes):
+ code = code.decode('utf-8')
+ u2.close()
+ if not code:
+ code = re.findall('(.*?)<\/pre>', p.replace('\n', '--endl--'))
+ if code:
+ # code = code[0]
+ # take the biggest code block
+ code = sorted(code, key=len)[-1]
+ code = code.replace('--endl--', '\n')
+ else:
+ app.Console.PrintWarning(translate("AddonsInstaller", "Unable to fetch the code of this macro."))
+ # Clean HTML escape codes.
+ try:
+ from HTMLParser import HTMLParser
+ except ImportError:
+ from html.parser import HTMLParser
+ if sys.version_info.major < 3:
+ code = code.decode('utf8')
+ try:
+ code = HTMLParser().unescape(code)
+ code = code.replace(b'\xc2\xa0'.decode("utf-8"), ' ')
+ except:
+ FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Unable to clean macro code: ") + code + '\n')
+ if sys.version_info.major < 3:
+ code = code.encode('utf8')
+ desc = re.findall("(.*?)<\/td>", p.replace('\n', ' '))
+ if desc:
+ desc = desc[0]
+ else:
+ app.Console.PrintWarning(translate("AddonsInstaller", "Unable to retrieve a description for this macro."))
+ desc = "No description available"
+ self.desc = desc
+ self.url = url
+ self.code = code
+ self.parsed = True
+
diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py
new file mode 100644
index 0000000000..14a6d5a579
--- /dev/null
+++ b/src/Mod/AddonManager/addonmanager_utilities.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+if sys.version_info.major < 3:
+ import urllib2
+else:
+ import urllib.request as urllib2
+
+from PySide import QtGui
+
+# Qt tanslation handling
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def translate(context, text, disambig=None):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def translate(context, text, disambig=None):
+ return QtGui.QApplication.translate(context, text, disambig)
+
+ssl_ctx = None
+try:
+ import ssl
+except ImportError:
+ pass
+else:
+ try:
+ ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ except AttributeError:
+ pass
+
+
+def urlopen(url):
+ """Opens an url with urllib2"""
+ if ssl_ctx:
+ u = urllib2.urlopen(url, context=ssl_ctx)
+ else:
+ u = urllib2.urlopen(url)
+ return u