AddonManager: update python source formatting
Update formatting in compliance with pep8 with the following exceptions: * truncate to 120 characters in line * prefer double quotes `"` to single quotes `'` in strings
This commit is contained in:
committed by
Yorik van Havre
parent
07db27d0dd
commit
ba69518c36
@@ -25,8 +25,23 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
__title__="FreeCAD Addon Manager Module"
|
||||
__author__ = "Yorik van Havre","Jonathan Wiedemann","Kurt Kremitzki"
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
import AddonManager_rc
|
||||
import FreeCADGui
|
||||
|
||||
from addonmanager_utilities import translate # this needs to be as is for pylupdate
|
||||
from addonmanager_workers import *
|
||||
import addonmanager_utilities as utils
|
||||
|
||||
__title__ = "FreeCAD Addon Manager Module"
|
||||
__author__ = "Yorik van Havre", "Jonathan Wiedemann", "Kurt Kremitzki"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
|
||||
"""
|
||||
@@ -37,58 +52,54 @@ You need a working internet connection, and optionally the GitPython package
|
||||
installed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_utilities import translate # this needs to be as is for pylupdate
|
||||
from addonmanager_workers import *
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctx,txt):
|
||||
return txt
|
||||
|
||||
## \defgroup ADDONMANAGER AddonManager
|
||||
# \defgroup ADDONMANAGER AddonManager
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief The Addon Manager allows to install workbenches and macros made by users
|
||||
# @{
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctx, txt):
|
||||
return txt
|
||||
|
||||
|
||||
class CommandAddonManager:
|
||||
"""The main Addon Manager class and FreeCAD command"""
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
return {'Pixmap': 'AddonManager',
|
||||
'MenuText': QT_TRANSLATE_NOOP("Std_AddonMgr", '&Addon manager'),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Std_AddonMgr", 'Manage external workbenches and macros'),
|
||||
'Group': 'Tools'}
|
||||
return {"Pixmap": "AddonManager",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Std_AddonMgr", "&Addon manager"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Std_AddonMgr", "Manage external workbenches and macros"),
|
||||
"Group": "Tools"}
|
||||
|
||||
def Activated(self):
|
||||
|
||||
# display first use dialog if needed
|
||||
|
||||
from PySide import QtGui
|
||||
readWarning = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetBool('readWarning',False)
|
||||
readWarning = FreeCAD.ParamGet("User parameter:Plugins/addonsRepository").GetBool("readWarning",
|
||||
False)
|
||||
if not readWarning:
|
||||
if QtGui.QMessageBox.warning(None,"FreeCAD",translate("AddonsInstaller", "The addons that can be installed here are not officially part of FreeCAD, and are not reviewed by the FreeCAD team. Make sure you know what you are installing!"), QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok) != QtGui.QMessageBox.StandardButton.Cancel:
|
||||
FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetBool('readWarning',True)
|
||||
if (QtGui.QMessageBox.warning(None,
|
||||
"FreeCAD",
|
||||
translate("AddonsInstaller",
|
||||
"The addons that can be installed here are not "
|
||||
"officially part of FreeCAD, and are not reviewed "
|
||||
"by the FreeCAD team. Make sure you know what you "
|
||||
"are installing!"),
|
||||
QtGui.QMessageBox.Cancel |
|
||||
QtGui.QMessageBox.Ok) !=
|
||||
QtGui.QMessageBox.StandardButton.Cancel):
|
||||
FreeCAD.ParamGet("User parameter:Plugins/addonsRepository").SetBool("readWarning",
|
||||
True)
|
||||
readWarning = True
|
||||
|
||||
if readWarning:
|
||||
self.launch()
|
||||
|
||||
def launch(self):
|
||||
|
||||
"""Shows the Addon Manager UI"""
|
||||
|
||||
import FreeCADGui
|
||||
from PySide import QtGui
|
||||
|
||||
# create the dialog
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),"AddonManager.ui"))
|
||||
self.dialog = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),
|
||||
"AddonManager.ui"))
|
||||
|
||||
# cleanup the leftovers from previous runs
|
||||
self.repos = []
|
||||
@@ -96,12 +107,13 @@ class CommandAddonManager:
|
||||
self.macro_repo_dir = tempfile.mkdtemp()
|
||||
self.doUpdate = []
|
||||
self.addon_removed = False
|
||||
for worker in ["update_worker","check_worker","show_worker","showmacro_worker","macro_worker","install_worker"]:
|
||||
if hasattr(self,worker):
|
||||
thread = getattr(self,worker)
|
||||
for worker in ["update_worker", "check_worker", "show_worker",
|
||||
"showmacro_worker", "macro_worker", "install_worker"]:
|
||||
if hasattr(self, worker):
|
||||
thread = getattr(self, worker)
|
||||
if thread:
|
||||
if thread.isFinished():
|
||||
setattr(self,worker,None)
|
||||
setattr(self, worker, None)
|
||||
self.dialog.tabWidget.setCurrentIndex(0)
|
||||
# these 2 settings to prevent loading an addon description on start (let the user click something first)
|
||||
self.firsttime = True
|
||||
@@ -109,24 +121,23 @@ class CommandAddonManager:
|
||||
|
||||
# restore window geometry and splitter state from stored state
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
w = pref.GetInt("WindowWidth",600)
|
||||
h = pref.GetInt("WindowHeight",480)
|
||||
self.dialog.resize(w,h)
|
||||
sl = pref.GetInt("SplitterLeft",298)
|
||||
sr = pref.GetInt("SplitterRight",274)
|
||||
self.dialog.splitter.setSizes([sl,sr])
|
||||
w = pref.GetInt("WindowWidth", 600)
|
||||
h = pref.GetInt("WindowHeight", 480)
|
||||
self.dialog.resize(w, h)
|
||||
sl = pref.GetInt("SplitterLeft", 298)
|
||||
sr = pref.GetInt("SplitterRight", 274)
|
||||
self.dialog.splitter.setSizes([sl, sr])
|
||||
|
||||
# set nice icons to everything, by theme with fallback to FreeCAD icons
|
||||
self.dialog.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
|
||||
self.dialog.buttonExecute.setIcon(QtGui.QIcon.fromTheme("execute",QtGui.QIcon(":/icons/button_valid.svg")))
|
||||
self.dialog.buttonUninstall.setIcon(QtGui.QIcon.fromTheme("cancel",QtGui.QIcon(":/icons/edit_Cancel.svg")))
|
||||
self.dialog.buttonInstall.setIcon(QtGui.QIcon.fromTheme("download",QtGui.QIcon(":/icons/edit_OK.svg")))
|
||||
self.dialog.buttonExecute.setIcon(QtGui.QIcon.fromTheme("execute", QtGui.QIcon(":/icons/button_valid.svg")))
|
||||
self.dialog.buttonUninstall.setIcon(QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")))
|
||||
self.dialog.buttonInstall.setIcon(QtGui.QIcon.fromTheme("download", QtGui.QIcon(":/icons/edit_OK.svg")))
|
||||
self.dialog.buttonUpdateAll.setIcon(QtGui.QIcon(":/icons/button_valid.svg"))
|
||||
self.dialog.buttonConfigure.setIcon(QtGui.QIcon(":/icons/preferences-system.svg"))
|
||||
self.dialog.buttonClose.setIcon(QtGui.QIcon.fromTheme("close",QtGui.QIcon(":/icons/process-stop.svg")))
|
||||
self.dialog.tabWidget.setTabIcon(0,QtGui.QIcon.fromTheme("folder",QtGui.QIcon(":/icons/folder.svg")))
|
||||
self.dialog.tabWidget.setTabIcon(1,QtGui.QIcon(":/icons/applications-python.svg"))
|
||||
|
||||
self.dialog.buttonClose.setIcon(QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")))
|
||||
self.dialog.tabWidget.setTabIcon(0, QtGui.QIcon.fromTheme("folder", QtGui.QIcon(":/icons/folder.svg")))
|
||||
self.dialog.tabWidget.setTabIcon(1, QtGui.QIcon(":/icons/applications-python.svg"))
|
||||
|
||||
# enable/disable stuff
|
||||
self.dialog.buttonExecute.setEnabled(False)
|
||||
@@ -161,52 +172,53 @@ class CommandAddonManager:
|
||||
self.dialog.exec_()
|
||||
|
||||
def reject(self):
|
||||
|
||||
"""called when the window has been closed"""
|
||||
|
||||
# save window geometry and splitter state for next use
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
pref.SetInt("WindowWidth",self.dialog.width())
|
||||
pref.SetInt("WindowHeight",self.dialog.height())
|
||||
pref.SetInt("SplitterLeft",self.dialog.splitter.sizes()[0])
|
||||
pref.SetInt("SplitterRight",self.dialog.splitter.sizes()[1])
|
||||
pref.SetInt("WindowWidth", self.dialog.width())
|
||||
pref.SetInt("WindowHeight", self.dialog.height())
|
||||
pref.SetInt("SplitterLeft", self.dialog.splitter.sizes()[0])
|
||||
pref.SetInt("SplitterRight", self.dialog.splitter.sizes()[1])
|
||||
|
||||
# ensure all threads are finished before closing
|
||||
oktoclose = True
|
||||
for worker in ["update_worker","check_worker","show_worker","showmacro_worker",
|
||||
"macro_worker","install_worker"]:
|
||||
if hasattr(self,worker):
|
||||
thread = getattr(self,worker)
|
||||
for worker in ["update_worker", "check_worker", "show_worker", "showmacro_worker",
|
||||
"macro_worker", "install_worker"]:
|
||||
if hasattr(self, worker):
|
||||
thread = getattr(self, worker)
|
||||
if thread:
|
||||
if not thread.isFinished():
|
||||
oktoclose = False
|
||||
|
||||
# all threads have finished
|
||||
if oktoclose:
|
||||
if (hasattr(self,"install_worker") and self.install_worker) or (hasattr(self,"addon_removed") and self.addon_removed):
|
||||
if ((hasattr(self, "install_worker") and self.install_worker) or
|
||||
(hasattr(self, "addon_removed") and self.addon_removed)):
|
||||
# display restart dialog
|
||||
from PySide import QtGui,QtCore
|
||||
m = QtGui.QMessageBox()
|
||||
m.setWindowTitle(translate("AddonsInstaller","Addon manager"))
|
||||
m.setWindowTitle(translate("AddonsInstaller", "Addon manager"))
|
||||
m.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
|
||||
m.setText(translate("AddonsInstaller","You must restart FreeCAD for changes to take effect. Press Ok to restart FreeCAD now, or Cancel to restart later."))
|
||||
m.setText(translate("AddonsInstaller",
|
||||
"You must restart FreeCAD for changes to take "
|
||||
"effect. Press Ok to restart FreeCAD now, or "
|
||||
"Cancel to restart later."))
|
||||
m.setIcon(m.Warning)
|
||||
m.setStandardButtons(m.Ok | m.Cancel)
|
||||
m.setDefaultButton(m.Cancel)
|
||||
ret = m.exec_()
|
||||
if ret == m.Ok:
|
||||
shutil.rmtree(self.macro_repo_dir,onerror=self.remove_readonly)
|
||||
shutil.rmtree(self.macro_repo_dir, onerror=self.remove_readonly)
|
||||
# restart FreeCAD after a delay to give time to this dialog to close
|
||||
QtCore.QTimer.singleShot(1000,utils.restartFreeCAD)
|
||||
QtCore.QTimer.singleShot(1000, utils.restart_freecad)
|
||||
try:
|
||||
shutil.rmtree(self.macro_repo_dir,onerror=self.remove_readonly)
|
||||
except:
|
||||
shutil.rmtree(self.macro_repo_dir, onerror=self.remove_readonly)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
|
||||
"""updates the list of workbenches"""
|
||||
|
||||
self.dialog.listWorkbenches.clear()
|
||||
@@ -220,17 +232,16 @@ class CommandAddonManager:
|
||||
self.update_worker.start()
|
||||
|
||||
def check_updates(self):
|
||||
|
||||
"checks every installed addon for available updates"
|
||||
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
if pref.GetBool("AutoCheck",False) and not self.doUpdate:
|
||||
if hasattr(self,"check_worker"):
|
||||
if pref.GetBool("AutoCheck", False) and not self.doUpdate:
|
||||
if hasattr(self, "check_worker"):
|
||||
thread = self.check_worker
|
||||
if thread:
|
||||
if not thread.isFinished():
|
||||
return
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller","Checking for updates..."))
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Checking for updates..."))
|
||||
self.check_worker = CheckWBWorker(self.repos)
|
||||
self.check_worker.mark.connect(self.mark)
|
||||
self.check_worker.enable.connect(self.enable_updates)
|
||||
@@ -238,44 +249,43 @@ class CommandAddonManager:
|
||||
self.check_worker.start()
|
||||
|
||||
def apply_updates(self):
|
||||
|
||||
"""apply all available updates"""
|
||||
|
||||
if self.doUpdate:
|
||||
self.install(self.doUpdate)
|
||||
self.dialog.buttonUpdateAll.setEnabled(False)
|
||||
|
||||
def enable_updates(self,num):
|
||||
|
||||
def enable_updates(self, num):
|
||||
"""enables the update button"""
|
||||
|
||||
if num:
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller","Apply")+" "+str(num)+" "+translate("AddonsInstaller","update(s)"))
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Apply") +
|
||||
" " + str(num) + " " +
|
||||
translate("AddonsInstaller", "update(s)"))
|
||||
self.dialog.buttonUpdateAll.setEnabled(True)
|
||||
else:
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller","No update available"))
|
||||
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "No update available"))
|
||||
self.dialog.buttonUpdateAll.setEnabled(False)
|
||||
|
||||
def add_addon_repo(self, addon_repo):
|
||||
|
||||
"""adds a workbench to the list"""
|
||||
|
||||
from PySide import QtGui
|
||||
self.repos.append(addon_repo)
|
||||
addonicon = self.get_icon(addon_repo[0])
|
||||
if addon_repo[2] > 0:
|
||||
item = QtGui.QListWidgetItem(addonicon,str(addon_repo[0]) + str(" ("+translate("AddonsInstaller","Installed")+")"))
|
||||
item.setForeground(QtGui.QBrush(QtGui.QColor(0,182,41)))
|
||||
item = QtGui.QListWidgetItem(addonicon,
|
||||
str(addon_repo[0]) +
|
||||
str(" (" +
|
||||
translate("AddonsInstaller", "Installed") +
|
||||
")"))
|
||||
item.setForeground(QtGui.QBrush(QtGui.QColor(0, 182, 41)))
|
||||
self.dialog.listWorkbenches.addItem(item)
|
||||
else:
|
||||
self.dialog.listWorkbenches.addItem(QtGui.QListWidgetItem(addonicon,str(addon_repo[0])))
|
||||
|
||||
def get_icon(self,repo):
|
||||
self.dialog.listWorkbenches.addItem(QtGui.QListWidgetItem(addonicon, str(addon_repo[0])))
|
||||
|
||||
def get_icon(self, repo):
|
||||
"""returns an icon for a repo"""
|
||||
|
||||
import AddonManager_rc
|
||||
from PySide import QtGui
|
||||
path = ":/icons/" + repo + "_workbench_icon.svg"
|
||||
if QtCore.QFile.exists(path):
|
||||
addonicon = QtGui.QIcon(path)
|
||||
@@ -286,7 +296,6 @@ class CommandAddonManager:
|
||||
return addonicon
|
||||
|
||||
def show_information(self, label):
|
||||
|
||||
"""shows text in the information pane"""
|
||||
|
||||
self.dialog.description.setText(label)
|
||||
@@ -295,8 +304,7 @@ class CommandAddonManager:
|
||||
else:
|
||||
self.dialog.listMacros.setFocus()
|
||||
|
||||
def show(self,idx):
|
||||
|
||||
def show(self, idx):
|
||||
"""loads information of a given workbench"""
|
||||
|
||||
# this function is triggered also when the list is populated, prevent that here
|
||||
@@ -306,7 +314,7 @@ class CommandAddonManager:
|
||||
return
|
||||
|
||||
if self.repos and idx >= 0:
|
||||
if hasattr(self,"show_worker"):
|
||||
if hasattr(self, "show_worker"):
|
||||
# kill existing show worker (might still be busy loading images...)
|
||||
if self.show_worker:
|
||||
self.show_worker.exit()
|
||||
@@ -323,8 +331,7 @@ class CommandAddonManager:
|
||||
self.dialog.buttonInstall.setEnabled(True)
|
||||
self.dialog.buttonUninstall.setEnabled(True)
|
||||
|
||||
def show_macro(self,idx):
|
||||
|
||||
def show_macro(self, idx):
|
||||
"""loads information of a given macro"""
|
||||
|
||||
# this function is triggered when the list is populated, prevent that here
|
||||
@@ -334,7 +341,7 @@ class CommandAddonManager:
|
||||
return
|
||||
|
||||
if self.macros and idx >= 0:
|
||||
if hasattr(self,"showmacro_worker"):
|
||||
if hasattr(self, "showmacro_worker"):
|
||||
if self.showmacro_worker:
|
||||
if not self.showmacro_worker.isFinished():
|
||||
self.showmacro_worker.exit()
|
||||
@@ -351,8 +358,7 @@ class CommandAddonManager:
|
||||
else:
|
||||
self.dialog.buttonExecute.setEnabled(False)
|
||||
|
||||
def switchtab(self,idx):
|
||||
|
||||
def switchtab(self, idx):
|
||||
"""does what needs to be done when switching tabs"""
|
||||
|
||||
if idx == 1:
|
||||
@@ -367,13 +373,11 @@ class CommandAddonManager:
|
||||
self.dialog.listMacros.setCurrentRow(0)
|
||||
|
||||
def update_repos(self, repos):
|
||||
|
||||
"""this function allows threads to update the main list of workbenches"""
|
||||
|
||||
self.repos = repos
|
||||
|
||||
def add_macro(self, macro):
|
||||
|
||||
"""adds a macro to the list"""
|
||||
|
||||
if macro.name:
|
||||
@@ -382,10 +386,8 @@ class CommandAddonManager:
|
||||
old_macro = self.macros[self.macros.index(macro)]
|
||||
utils.update_macro_details(old_macro, macro)
|
||||
else:
|
||||
from PySide import QtGui
|
||||
self.macros.append(macro)
|
||||
import AddonManager_rc
|
||||
path = ":/icons/" + macro.name.replace(" ","_") + "_macro_icon.svg"
|
||||
path = ":/icons/" + macro.name.replace(" ", "_") + "_macro_icon.svg"
|
||||
if QtCore.QFile.exists(path):
|
||||
addonicon = QtGui.QIcon(path)
|
||||
else:
|
||||
@@ -393,14 +395,13 @@ class CommandAddonManager:
|
||||
if addonicon.isNull():
|
||||
addonicon = QtGui.QIcon(":/icons/document-python.svg")
|
||||
if macro.is_installed():
|
||||
item = QtGui.QListWidgetItem(addonicon, macro.name + str(' (Installed)'))
|
||||
item.setForeground(QtGui.QBrush(QtGui.QColor(0,182,41)))
|
||||
item = QtGui.QListWidgetItem(addonicon, macro.name + str(" (Installed)"))
|
||||
item.setForeground(QtGui.QBrush(QtGui.QColor(0, 182, 41)))
|
||||
self.dialog.listMacros.addItem(item)
|
||||
else:
|
||||
self.dialog.listMacros.addItem(QtGui.QListWidgetItem(addonicon,macro.name))
|
||||
|
||||
def install(self,repos=None):
|
||||
self.dialog.listMacros.addItem(QtGui.QListWidgetItem(addonicon, macro.name))
|
||||
|
||||
def install(self, repos=None):
|
||||
"""installs a workbench or macro"""
|
||||
|
||||
if self.dialog.tabWidget.currentIndex() == 0:
|
||||
@@ -409,13 +410,13 @@ class CommandAddonManager:
|
||||
if repos:
|
||||
idx = []
|
||||
for repo in repos:
|
||||
for i,r in enumerate(self.repos):
|
||||
for i, r in enumerate(self.repos):
|
||||
if r[0] == repo:
|
||||
idx.append(i)
|
||||
else:
|
||||
idx = self.dialog.listWorkbenches.currentRow()
|
||||
if idx != None:
|
||||
if hasattr(self,"install_worker") and self.install_worker:
|
||||
if idx is not None:
|
||||
if hasattr(self, "install_worker") and self.install_worker:
|
||||
if self.install_worker.isRunning():
|
||||
return
|
||||
self.install_worker = InstallWorker(self.repos, idx)
|
||||
@@ -428,15 +429,16 @@ class CommandAddonManager:
|
||||
# Tab "Macros".
|
||||
macro = self.macros[self.dialog.listMacros.currentRow()]
|
||||
if utils.install_macro(macro, self.macro_repo_dir):
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Macro successfully installed. The macro is now available from the Macros dialog."))
|
||||
self.dialog.description.setText(translate("AddonsInstaller",
|
||||
"Macro successfully installed. The macro is "
|
||||
"now available from the Macros dialog."))
|
||||
else:
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Unable to install"))
|
||||
|
||||
def show_progress_bar(self, state):
|
||||
|
||||
"""shows or hides the progress bar"""
|
||||
|
||||
if state == True:
|
||||
if state:
|
||||
self.dialog.tabWidget.setEnabled(False)
|
||||
self.dialog.buttonInstall.setEnabled(False)
|
||||
self.dialog.buttonUninstall.setEnabled(False)
|
||||
@@ -453,10 +455,8 @@ class CommandAddonManager:
|
||||
self.dialog.listMacros.setFocus()
|
||||
|
||||
def executemacro(self):
|
||||
|
||||
"""executes a selected macro"""
|
||||
|
||||
import FreeCADGui
|
||||
if self.dialog.tabWidget.currentIndex() == 1:
|
||||
# Tab "Macros".
|
||||
macro = self.macros[self.dialog.listMacros.currentRow()]
|
||||
@@ -465,7 +465,7 @@ class CommandAddonManager:
|
||||
return
|
||||
macro_path = os.path.join(FreeCAD.getUserMacroDir(True), macro.filename)
|
||||
if os.path.exists(macro_path):
|
||||
macro_path = macro_path.replace("\\","/")
|
||||
macro_path = macro_path.replace("\\", "/")
|
||||
|
||||
FreeCADGui.open(str(macro_path))
|
||||
self.dialog.hide()
|
||||
@@ -481,7 +481,6 @@ class CommandAddonManager:
|
||||
func(path)
|
||||
|
||||
def remove(self):
|
||||
|
||||
"""uninstalls a macro or workbench"""
|
||||
|
||||
if self.dialog.tabWidget.currentIndex() == 0:
|
||||
@@ -492,7 +491,8 @@ class CommandAddonManager:
|
||||
clonedir = moddir + os.sep + self.repos[idx][0]
|
||||
if os.path.exists(clonedir):
|
||||
shutil.rmtree(clonedir, onerror=self.remove_readonly)
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Addon successfully removed. Please restart FreeCAD"))
|
||||
self.dialog.description.setText(translate("AddonsInstaller",
|
||||
"Addon successfully removed. Please restart FreeCAD"))
|
||||
else:
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Unable to remove this addon"))
|
||||
|
||||
@@ -500,60 +500,59 @@ class CommandAddonManager:
|
||||
# Tab "Macros".
|
||||
macro = self.macros[self.dialog.listMacros.currentRow()]
|
||||
if utils.remove_macro(macro):
|
||||
self.dialog.description.setText(translate('AddonsInstaller', 'Macro successfully removed.'))
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Macro successfully removed."))
|
||||
else:
|
||||
self.dialog.description.setText(translate('AddonsInstaller', 'Macro could not be removed.'))
|
||||
self.dialog.description.setText(translate("AddonsInstaller", "Macro could not be removed."))
|
||||
self.update_status(soft=True)
|
||||
self.addon_removed = True # A value to trigger the restart message on dialog close
|
||||
|
||||
def mark_recompute(self,addon):
|
||||
self.addon_removed = True # A value to trigger the restart message on dialog close
|
||||
|
||||
def mark_recompute(self, addon):
|
||||
"""marks an addon in the list as installed but needs recompute"""
|
||||
|
||||
for i in range(self.dialog.listWorkbenches.count()):
|
||||
txt = self.dialog.listWorkbenches.item(i).text().strip()
|
||||
if txt.endswith(" ("+translate("AddonsInstaller","Installed")+")"):
|
||||
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
|
||||
txt = txt[:-12]
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller","Update available")+")"):
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
|
||||
txt = txt[:-19]
|
||||
if txt == addon:
|
||||
from PySide import QtGui
|
||||
self.dialog.listWorkbenches.item(i).setText(txt+" ("+translate("AddonsInstaller","Restart required")+")")
|
||||
self.dialog.listWorkbenches.item(i).setText(txt + " (" +
|
||||
translate("AddonsInstaller",
|
||||
"Restart required") +
|
||||
")")
|
||||
self.dialog.listWorkbenches.item(i).setIcon(QtGui.QIcon(":/icons/edit-undo.svg"))
|
||||
|
||||
def update_status(self,soft=False):
|
||||
|
||||
def update_status(self, soft=False):
|
||||
"""Updates the list of workbenches/macros. If soft is true, items
|
||||
are not recreated (and therefore display text isn't triggered)"
|
||||
"""
|
||||
|
||||
moddir = FreeCAD.getUserAppDataDir() + os.sep + "Mod"
|
||||
from PySide import QtGui
|
||||
if soft:
|
||||
for i in range(self.dialog.listWorkbenches.count()):
|
||||
txt = self.dialog.listWorkbenches.item(i).text().strip()
|
||||
ext = ""
|
||||
if txt.endswith(" ("+translate("AddonsInstaller","Installed")+")"):
|
||||
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
|
||||
txt = txt[:-12]
|
||||
ext = " ("+translate("AddonsInstaller","Installed")+")"
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller","Update available")+")"):
|
||||
ext = " ("+translate("AddonsInstaller", "Installed")+")"
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
|
||||
txt = txt[:-19]
|
||||
ext = " ("+translate("AddonsInstaller","Update available")+")"
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller","Restart required")+")"):
|
||||
ext = " ("+translate("AddonsInstaller", "Update available")+")"
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller", "Restart required")+")"):
|
||||
txt = txt[:-19]
|
||||
ext = " ("+translate("AddonsInstaller","Restart required")+")"
|
||||
if os.path.exists(os.path.join(moddir,txt)):
|
||||
ext = " ("+translate("AddonsInstaller", "Restart required")+")"
|
||||
if os.path.exists(os.path.join(moddir, txt)):
|
||||
self.dialog.listWorkbenches.item(i).setText(txt+ext)
|
||||
else:
|
||||
self.dialog.listWorkbenches.item(i).setText(txt)
|
||||
self.dialog.listWorkbenches.item(i).setIcon(self.get_icon(txt))
|
||||
for i in range(self.dialog.listMacros.count()):
|
||||
txt = self.dialog.listMacros.item(i).text().strip()
|
||||
if txt.endswith(" ("+translate("AddonsInstaller","Installed")+")"):
|
||||
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
|
||||
txt = txt[:-12]
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller","Update available")+")"):
|
||||
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
|
||||
txt = txt[:-19]
|
||||
if os.path.exists(os.path.join(moddir,txt)):
|
||||
if os.path.exists(os.path.join(moddir, txt)):
|
||||
self.dialog.listMacros.item(i).setText(txt+ext)
|
||||
else:
|
||||
self.dialog.listMacros.item(i).setText(txt)
|
||||
@@ -562,69 +561,71 @@ class CommandAddonManager:
|
||||
self.dialog.listWorkbenches.clear()
|
||||
self.dialog.listMacros.clear()
|
||||
for wb in self.repos:
|
||||
if os.path.exists(os.path.join(moddir,wb[0])):
|
||||
self.dialog.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon(":/icons/button_valid.svg"),str(wb[0]) + " ("+translate("AddonsInstaller","Installed")+")"))
|
||||
if os.path.exists(os.path.join(moddir, wb[0])):
|
||||
self.dialog.listWorkbenches.addItem(
|
||||
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/button_valid.svg"),
|
||||
str(wb[0]) + " (" +
|
||||
translate("AddonsInstaller", "Installed") + ")"))
|
||||
wb[2] = 1
|
||||
else:
|
||||
self.dialog.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"),str(wb[0])))
|
||||
self.dialog.listWorkbenches.addItem(
|
||||
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"), str(wb[0])))
|
||||
wb[2] = 0
|
||||
for macro in self.macros:
|
||||
if macro.is_installed():
|
||||
self.dialog.listMacros.addItem(item)
|
||||
else:
|
||||
self.dialog.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"),+macro.name))
|
||||
|
||||
def mark(self,repo):
|
||||
self.dialog.listMacros.addItem(
|
||||
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"), macro.name))
|
||||
|
||||
def mark(self, repo):
|
||||
"""mark a workbench as updatable"""
|
||||
|
||||
from PySide import QtGui
|
||||
for i in range(self.dialog.listWorkbenches.count()):
|
||||
w = self.dialog.listWorkbenches.item(i)
|
||||
if (w.text() == str(repo)) or w.text().startswith(str(repo)+" "):
|
||||
w.setText(str(repo) + str(" ("+translate("AddonsInstaller","Update available")+")"))
|
||||
w.setForeground(QtGui.QBrush(QtGui.QColor(182,90,0)))
|
||||
if not repo in self.doUpdate:
|
||||
w.setText(str(repo) + str(" ("+translate("AddonsInstaller", "Update available")+")"))
|
||||
w.setForeground(QtGui.QBrush(QtGui.QColor(182, 90, 0)))
|
||||
if repo not in self.doUpdate:
|
||||
self.doUpdate.append(repo)
|
||||
|
||||
def show_config(self):
|
||||
|
||||
"""shows the configuration dialog"""
|
||||
|
||||
import FreeCADGui
|
||||
from PySide import QtGui
|
||||
self.config = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),"AddonManagerOptions.ui"))
|
||||
self.config = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "AddonManagerOptions.ui"))
|
||||
|
||||
# restore stored values
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
self.config.checkUpdates.setChecked(pref.GetBool("AutoCheck",False))
|
||||
self.config.customRepositories.setPlainText(pref.GetString("CustomRepositories",""))
|
||||
self.config.radioButtonNoProxy.setChecked(pref.GetBool("NoProxyCheck",True))
|
||||
self.config.radioButtonSystemProxy.setChecked(pref.GetBool("SystemProxyCheck",False))
|
||||
self.config.radioButtonUserProxy.setChecked(pref.GetBool("UserProxyCheck",False))
|
||||
self.config.userProxy.setPlainText(pref.GetString("ProxyUrl",""))
|
||||
self.config.checkUpdates.setChecked(pref.GetBool("AutoCheck", False))
|
||||
self.config.customRepositories.setPlainText(pref.GetString("CustomRepositories", ""))
|
||||
self.config.radioButtonNoProxy.setChecked(pref.GetBool("NoProxyCheck", True))
|
||||
self.config.radioButtonSystemProxy.setChecked(pref.GetBool("SystemProxyCheck", False))
|
||||
self.config.radioButtonUserProxy.setChecked(pref.GetBool("UserProxyCheck", False))
|
||||
self.config.userProxy.setPlainText(pref.GetString("ProxyUrl", ""))
|
||||
|
||||
# center the dialog over the Addon Manager
|
||||
self.config.move(self.dialog.frameGeometry().topLeft() + self.dialog.rect().center() - self.config.rect().center())
|
||||
self.config.move(self.dialog.frameGeometry().topLeft() +
|
||||
self.dialog.rect().center() -
|
||||
self.config.rect().center())
|
||||
|
||||
ret = self.config.exec_()
|
||||
|
||||
if ret:
|
||||
# OK button has been pressed
|
||||
pref.SetBool("AutoCheck",self.config.checkUpdates.isChecked())
|
||||
pref.SetString("CustomRepositories",self.config.customRepositories.toPlainText())
|
||||
pref.SetBool("NoProxyCheck",self.config.radioButtonNoProxy.isChecked())
|
||||
pref.SetBool("SystemProxyCheck",self.config.radioButtonSystemProxy.isChecked())
|
||||
pref.SetBool("UserProxyCheck",self.config.radioButtonUserProxy.isChecked())
|
||||
pref.SetString("ProxyUrl",self.config.userProxy.toPlainText())
|
||||
pref.SetBool("AutoCheck", self.config.checkUpdates.isChecked())
|
||||
pref.SetString("CustomRepositories", self.config.customRepositories.toPlainText())
|
||||
pref.SetBool("NoProxyCheck", self.config.radioButtonNoProxy.isChecked())
|
||||
pref.SetBool("SystemProxyCheck", self.config.radioButtonSystemProxy.isChecked())
|
||||
pref.SetBool("UserProxyCheck", self.config.radioButtonUserProxy.isChecked())
|
||||
pref.SetString("ProxyUrl", self.config.userProxy.toPlainText())
|
||||
|
||||
def check_updates(addon_name,callback):
|
||||
|
||||
def check_updates(addon_name, callback):
|
||||
"""Checks for updates for a given addon"""
|
||||
|
||||
oname = "update_checker_"+addon_name
|
||||
setattr(FreeCAD,oname,CheckSingleWorker(addon_name))
|
||||
getattr(FreeCAD,oname).updateAvailable.connect(callback)
|
||||
getattr(FreeCAD,oname).start()
|
||||
setattr(FreeCAD, oname, CheckSingleWorker(addon_name))
|
||||
getattr(FreeCAD, oname).updateAvailable.connect(callback)
|
||||
getattr(FreeCAD, oname).start()
|
||||
|
||||
## @}
|
||||
# @}
|
||||
|
||||
@@ -21,18 +21,50 @@
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
## @package AddonManager_macro
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Unified handler for FreeCAD macros that can be obtained from different sources
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from addonmanager_utilities import translate
|
||||
from addonmanager_utilities import urlopen
|
||||
|
||||
try:
|
||||
from HTMLParser import HTMLParser
|
||||
unescape = HTMLParser().unescape
|
||||
except ImportError:
|
||||
from html import unescape
|
||||
|
||||
try:
|
||||
import StringIO as io
|
||||
_stringio = io.StringIO
|
||||
except ImportError: # StringIO is not available with python3
|
||||
import io
|
||||
_stringio = io.BytesIO
|
||||
|
||||
have_git = False
|
||||
try:
|
||||
import git
|
||||
have_git = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
have_zip = False
|
||||
try:
|
||||
import zipfile
|
||||
have_zip = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# @package AddonManager_macro
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Unified handler for FreeCAD macros that can be obtained from different sources
|
||||
# @{
|
||||
|
||||
|
||||
class Macro(object):
|
||||
"""This class provides a unified way to handle macros coming from different sources"""
|
||||
@@ -41,11 +73,11 @@ class Macro(object):
|
||||
self.name = name
|
||||
self.on_wiki = False
|
||||
self.on_git = False
|
||||
self.desc = ''
|
||||
self.code = ''
|
||||
self.url = ''
|
||||
self.version = ''
|
||||
self.src_filename = ''
|
||||
self.desc = ""
|
||||
self.code = ""
|
||||
self.url = ""
|
||||
self.version = ""
|
||||
self.src_filename = ""
|
||||
self.other_files = []
|
||||
self.parsed = False
|
||||
|
||||
@@ -56,13 +88,13 @@ class Macro(object):
|
||||
def filename(self):
|
||||
if self.on_git:
|
||||
return os.path.basename(self.src_filename)
|
||||
return (self.name + '.FCMacro').replace(' ', '_')
|
||||
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(FreeCAD.getUserMacroDir(True), self.filename))
|
||||
or os.path.exists(os.path.join(FreeCAD.getUserMacroDir(True), 'Macro_' + self.filename)))
|
||||
or os.path.exists(os.path.join(FreeCAD.getUserMacroDir(True), "Macro_" + self.filename)))
|
||||
|
||||
def fill_details_from_file(self, filename):
|
||||
with open(filename) as f:
|
||||
@@ -73,22 +105,22 @@ class Macro(object):
|
||||
re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1")
|
||||
re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1")
|
||||
re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1")
|
||||
for l in f.readlines():
|
||||
match = re.match(re_desc, l)
|
||||
for line in f.readlines():
|
||||
match = re.match(re_desc, line)
|
||||
if match:
|
||||
self.desc = match.group(2)
|
||||
number_of_required_fields -= 1
|
||||
match = re.match(re_url, l)
|
||||
match = re.match(re_url, line)
|
||||
if match:
|
||||
self.url = match.group(2)
|
||||
number_of_required_fields -= 1
|
||||
match = re.match(re_version, l)
|
||||
match = re.match(re_version, line)
|
||||
if match:
|
||||
self.version = match.group(2)
|
||||
number_of_required_fields -= 1
|
||||
match = re.match(re_files, l)
|
||||
match = re.match(re_files, line)
|
||||
if match:
|
||||
self.other_files = [of.strip() for of in match.group(2).split(',')]
|
||||
self.other_files = [of.strip() for of in match.group(2).split(",")]
|
||||
number_of_required_fields -= 1
|
||||
if number_of_required_fields <= 0:
|
||||
break
|
||||
@@ -98,75 +130,65 @@ class Macro(object):
|
||||
|
||||
def fill_details_from_wiki(self, url):
|
||||
code = ""
|
||||
try:
|
||||
u = urlopen(url)
|
||||
except:
|
||||
print("AddonManager: Debug: unable to open URL",url)
|
||||
return
|
||||
if u is None :
|
||||
print("AddonManager: Debug: connection is lost (proxy setting changed?)",url)
|
||||
u = urlopen(url)
|
||||
if u is None:
|
||||
print("AddonManager: Debug: connection is lost (proxy setting changed?)", url)
|
||||
return
|
||||
p = u.read()
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode('utf-8')
|
||||
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)
|
||||
rawcodeurl = re.findall("rawcodeurl.*?href=\"(http.*?)\">", p)
|
||||
if rawcodeurl:
|
||||
rawcodeurl = rawcodeurl[0]
|
||||
try:
|
||||
u2 = urlopen(rawcodeurl)
|
||||
except:
|
||||
print("AddonManager: Debug: unable to open URL",rawcodeurl)
|
||||
u2 = urlopen(rawcodeurl)
|
||||
if u2 is None:
|
||||
print("AddonManager: Debug: unable to open URL", rawcodeurl)
|
||||
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))
|
||||
# expected = int(u2.headers["content-length"])
|
||||
while True:
|
||||
# print("expected:", expected, "got:", len(response))
|
||||
data = u2.read(block)
|
||||
if not data:
|
||||
break
|
||||
if sys.version_info.major >= 3 and isinstance(data, bytes):
|
||||
data = data.decode('utf-8')
|
||||
data = data.decode("utf-8")
|
||||
response += data
|
||||
if response:
|
||||
code = response
|
||||
u2.close()
|
||||
if not code:
|
||||
code = re.findall('<pre>(.*?)<\/pre>', p.replace('\n', '--endl--'))
|
||||
code = re.findall(r"<pre>(.*?)</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')
|
||||
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')
|
||||
code = code.decode("utf8")
|
||||
code = unescape(code)
|
||||
code = code.replace(b"\xc2\xa0".decode("utf-8"), " ")
|
||||
if sys.version_info.major < 3:
|
||||
code = code.encode('utf8')
|
||||
desc = re.findall("<td class=\"ctEven left macro-description\">(.*?)<\/td>", p.replace('\n', ' '))
|
||||
code = code.encode("utf8")
|
||||
desc = re.findall(r"<td class=\"ctEven left macro-description\">(.*?)</td>", p.replace("\n", " "))
|
||||
if desc:
|
||||
desc = desc[0]
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Unable to retrieve a description for this macro."))
|
||||
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
|
||||
|
||||
# @}
|
||||
|
||||
@@ -21,16 +21,26 @@
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
import FreeCAD
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
## @package AddonManager_utilities
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Utilities to work across different platforms, providers and python versions
|
||||
if sys.version_info.major < 3:
|
||||
import urllib2
|
||||
from urllib2 import URLError
|
||||
from urlparse import urlparse
|
||||
else:
|
||||
import urllib.request as urllib2
|
||||
from urllib.error import URLError
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
# check for SSL support
|
||||
|
||||
@@ -44,14 +54,17 @@ else:
|
||||
ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
# @package AddonManager_utilities
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Utilities to work across different platforms, providers and python versions
|
||||
# @{
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
|
||||
"Main translation function"
|
||||
|
||||
from PySide import QtGui
|
||||
|
||||
try:
|
||||
_encoding = QtGui.QApplication.UnicodeUTF8
|
||||
except AttributeError:
|
||||
@@ -61,18 +74,16 @@ def translate(context, text, disambig=None):
|
||||
|
||||
|
||||
def symlink(source, link_name):
|
||||
|
||||
"creates a symlink of a file, if possible"
|
||||
|
||||
if os.path.exists(link_name) or os.path.lexists(link_name):
|
||||
#print("macro already exists")
|
||||
# print("macro already exists")
|
||||
pass
|
||||
else:
|
||||
os_symlink = getattr(os, "symlink", None)
|
||||
if callable(os_symlink):
|
||||
os_symlink(source, link_name)
|
||||
else:
|
||||
import ctypes
|
||||
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
|
||||
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
|
||||
csl.restype = ctypes.c_ubyte
|
||||
@@ -85,59 +96,48 @@ def symlink(source, link_name):
|
||||
|
||||
|
||||
def urlopen(url):
|
||||
|
||||
"""Opens an url with urllib2"""
|
||||
|
||||
timeout = 5
|
||||
|
||||
if sys.version_info.major < 3:
|
||||
import urllib2
|
||||
else:
|
||||
import urllib.request as urllib2
|
||||
|
||||
# Proxy an ssl configuration
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
if pref.GetBool("NoProxyCheck",True):
|
||||
proxies = {}
|
||||
if pref.GetBool("NoProxyCheck", True):
|
||||
proxies = {}
|
||||
else:
|
||||
if pref.GetBool("SystemProxyCheck",False):
|
||||
proxy = urllib2.getproxies()
|
||||
proxies = {"http": proxy.get('http'),"https": proxy.get('http')}
|
||||
elif pref.GetBool("UserProxyCheck",False):
|
||||
proxy = pref.GetString("ProxyUrl","")
|
||||
proxies = {"http": proxy, "https": proxy}
|
||||
if pref.GetBool("SystemProxyCheck", False):
|
||||
proxy = urllib2.getproxies()
|
||||
proxies = {"http": proxy.get('http'), "https": proxy.get('http')}
|
||||
elif pref.GetBool("UserProxyCheck", False):
|
||||
proxy = pref.GetString("ProxyUrl", "")
|
||||
proxies = {"http": proxy, "https": proxy}
|
||||
|
||||
if ssl_ctx:
|
||||
handler = urllib2.HTTPSHandler(context=ssl_ctx)
|
||||
else:
|
||||
handler = {}
|
||||
proxy_support = urllib2.ProxyHandler(proxies)
|
||||
proxy_support = urllib2.ProxyHandler(proxies)
|
||||
opener = urllib2.build_opener(proxy_support, handler)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
# Url opening
|
||||
req = urllib2.Request(url,
|
||||
headers={'User-Agent' : "Magic Browser"})
|
||||
headers={'User-Agent': "Magic Browser"})
|
||||
try:
|
||||
u = urllib2.urlopen(req, timeout=timeout)
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return u
|
||||
|
||||
def getserver(url):
|
||||
|
||||
def getserver(url):
|
||||
"""returns the server part of an url"""
|
||||
|
||||
if sys.version_info.major < 3:
|
||||
from urlparse import urlparse
|
||||
else:
|
||||
from urllib.parse import urlparse
|
||||
return '{uri.scheme}://{uri.netloc}/'.format(uri=urlparse(url))
|
||||
|
||||
|
||||
def update_macro_details(old_macro, new_macro):
|
||||
|
||||
"""Update a macro with information from another one
|
||||
|
||||
Update a macro with information from another one, supposedly the same but
|
||||
@@ -157,7 +157,6 @@ def update_macro_details(old_macro, new_macro):
|
||||
|
||||
|
||||
def install_macro(macro, macro_repo_dir):
|
||||
|
||||
"""Install a macro and all its related files
|
||||
|
||||
Returns True if the macro was installed correctly.
|
||||
@@ -201,7 +200,6 @@ def install_macro(macro, macro_repo_dir):
|
||||
|
||||
|
||||
def remove_macro(macro):
|
||||
|
||||
"""Remove a macro and all its related files
|
||||
|
||||
Returns True if the macro was removed correctly.
|
||||
@@ -231,7 +229,6 @@ def remove_macro(macro):
|
||||
|
||||
|
||||
def remove_directory_if_empty(dir):
|
||||
|
||||
"""Remove the directory if it is empty
|
||||
|
||||
Directory FreeCAD.getUserMacroDir(True) will not be removed even if empty.
|
||||
@@ -243,21 +240,17 @@ def remove_directory_if_empty(dir):
|
||||
os.rmdir(dir)
|
||||
|
||||
|
||||
def restartFreeCAD():
|
||||
|
||||
def restart_freecad():
|
||||
"Shuts down and restarts FreeCAD"
|
||||
|
||||
import FreeCADGui
|
||||
from PySide import QtGui,QtCore
|
||||
args = QtGui.QApplication.arguments()[1:]
|
||||
if FreeCADGui.getMainWindow().close():
|
||||
QtCore.QProcess.startDetached(QtGui.QApplication.applicationFilePath(),args)
|
||||
QtCore.QProcess.startDetached(QtGui.QApplication.applicationFilePath(), args)
|
||||
|
||||
|
||||
def getZipUrl(baseurl):
|
||||
|
||||
def get_zip_url(baseurl):
|
||||
"Returns the location of a zip file from a repo, if available"
|
||||
|
||||
|
||||
url = getserver(baseurl).strip("/")
|
||||
if url.endswith("github.com"):
|
||||
return baseurl+"/archive/master.zip"
|
||||
@@ -266,59 +259,54 @@ def getZipUrl(baseurl):
|
||||
reponame = baseurl.strip("/").split("/")[-1]
|
||||
return baseurl+"/-/archive/master/"+reponame+"-master.zip"
|
||||
else:
|
||||
print("Debug: addonmanager_utilities.getZipUrl: Unknown git host:",url)
|
||||
print("Debug: addonmanager_utilities.get_zip_url: Unknown git host:", url)
|
||||
return None
|
||||
|
||||
|
||||
def getReadmeUrl(url):
|
||||
|
||||
def get_readme_url(url):
|
||||
"Returns the location of a readme file"
|
||||
|
||||
if "github" in url or "framagit" in url or "gitlab" in url:
|
||||
return url+"/raw/master/README.md"
|
||||
else:
|
||||
print("Debug: addonmanager_utilities.getReadmeUrl: Unknown git host:",url)
|
||||
print("Debug: addonmanager_utilities.get_readme_url: Unknown git host:", url)
|
||||
return None
|
||||
|
||||
|
||||
def getDescRegex(url):
|
||||
|
||||
def get_desc_regex(url):
|
||||
"""Returns a regex string that extracts a WB description to be displayed in the description
|
||||
panel of the Addon manager, if the README could not be found"""
|
||||
|
||||
if "github" in url:
|
||||
return "<meta property=\"og:description\" content=\"(.*?)\""
|
||||
return r'<meta property="og:description" content="(.*?)"'
|
||||
elif "framagit" in url or "gitlab" in url:
|
||||
return "<meta.*?content=\"(.*?)\".*?og\:description.*?>"
|
||||
print("Debug: addonmanager_utilities.getDescRegex: Unknown git host:",url)
|
||||
return r'<meta.*?content="(.*?)".*?og:description.*?>'
|
||||
print("Debug: addonmanager_utilities.get_desc_regex: Unknown git host:", url)
|
||||
return None
|
||||
|
||||
|
||||
def getReadmeHTMLUrl(url):
|
||||
|
||||
"Returns the location of a html file containing readme"
|
||||
def get_readme_html_url(url):
|
||||
"""Returns the location of a html file containing readme"""
|
||||
|
||||
if ("github" in url):
|
||||
return url+"/blob/master/README.md"
|
||||
if "github" in url:
|
||||
return url + "/blob/master/README.md"
|
||||
else:
|
||||
print("Debug: addonmanager_utilities.getReadmeUrl: Unknown git host:",url)
|
||||
print("Debug: addonmanager_utilities.get_readme_html_url: Unknown git host:", url)
|
||||
return None
|
||||
|
||||
|
||||
def getReadmeRegex(url):
|
||||
|
||||
def get_readme_regex(url):
|
||||
"""Return a regex string that extracts the contents to be displayed in the description
|
||||
panel of the Addon manager, from raw HTML data (the readme's html rendering usually)"""
|
||||
|
||||
|
||||
if ("github" in url):
|
||||
return "<article.*?>(.*?)</article>"
|
||||
else:
|
||||
print("Debug: addonmanager_utilities.getReadmeRegex: Unknown git host:",url)
|
||||
print("Debug: addonmanager_utilities.get_readme_regex: Unknown git host:", url)
|
||||
return None
|
||||
|
||||
|
||||
def fixRelativeLinks(text, base_url):
|
||||
|
||||
def fix_relative_links(text, base_url):
|
||||
"""Replace markdown image relative links with
|
||||
absolute ones using the base URL"""
|
||||
|
||||
@@ -333,3 +321,5 @@ def fixRelativeLinks(text, base_url):
|
||||
print("Debug: replaced " + link + " with " + newlink)
|
||||
new_text = new_text + '\n' + line
|
||||
return new_text
|
||||
|
||||
# @}
|
||||
|
||||
@@ -27,17 +27,40 @@ import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import FreeCAD
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
import FreeCAD
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_utilities import translate # this needs to be as is for pylupdate
|
||||
from addonmanager_utilities import translate # this needs to be as is for pylupdate
|
||||
from addonmanager_macro import Macro
|
||||
|
||||
## @package AddonManager_workers
|
||||
have_git = False
|
||||
try:
|
||||
import git
|
||||
have_git = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
have_zip = False
|
||||
try:
|
||||
import zipfile
|
||||
have_zip = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
have_markdown = False
|
||||
try:
|
||||
import markdown
|
||||
have_markdown = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# @package AddonManager_workers
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Multithread workers for the addon manager
|
||||
# @{
|
||||
|
||||
# Blacklisted addons
|
||||
macros_blacklist = []
|
||||
@@ -48,9 +71,9 @@ obsolete = []
|
||||
# These addons will print an additional message informing the user Python2 only
|
||||
py2only = []
|
||||
|
||||
NOGIT = False # for debugging purposes, set this to True to always use http downloads
|
||||
NOGIT = False # for debugging purposes, set this to True to always use http downloads
|
||||
|
||||
NOMARKDOWN = False # for debugging purposes, set this to True to disable Markdown lib
|
||||
NOMARKDOWN = False # for debugging purposes, set this to True to disable Markdown lib
|
||||
|
||||
"""Multithread workers for the Addon Manager"""
|
||||
|
||||
@@ -68,7 +91,6 @@ class UpdateWorker(QtCore.QThread):
|
||||
QtCore.QThread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
|
||||
"populates the list of addons"
|
||||
|
||||
self.progressbar_show.emit(True)
|
||||
@@ -82,17 +104,17 @@ class UpdateWorker(QtCore.QThread):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
hit = re.findall(r'"obsolete"[^\{]*?{[^\{]*?"Mod":\[(?P<obsolete>[^\[\]]+?)\]}',
|
||||
p.replace('\n', '').replace(' ', ''))
|
||||
p.replace("\n", "").replace(" ", ""))
|
||||
if hit:
|
||||
obsolete = hit[0].replace('"', '').split(',')
|
||||
obsolete = hit[0].replace('"', "").split(",")
|
||||
hit = re.findall(r'"blacklisted"[^\{]*?{[^\{]*?"Macro":\[(?P<blacklisted>[^\[\]]+?)\]}',
|
||||
p.replace('\n', '').replace(' ', ''))
|
||||
p.replace("\n", "").replace(" ", ""))
|
||||
if hit:
|
||||
macros_blacklist = hit[0].replace('"', '').split(',')
|
||||
macros_blacklist = hit[0].replace('"', "").split(",")
|
||||
hit = re.findall(r'"py2only"[^\{]*?{[^\{]*?"Mod":\[(?P<py2only>[^\[\]]+?)\]}',
|
||||
p.replace('\n', '').replace(' ', ''))
|
||||
p.replace("\n", "").replace(" ", ""))
|
||||
if hit:
|
||||
py2only = hit[0].replace('"', '').split(',')
|
||||
py2only = hit[0].replace('"', "").split(",")
|
||||
else:
|
||||
print("Debug: addon_flags.json not found")
|
||||
|
||||
@@ -106,7 +128,7 @@ class UpdateWorker(QtCore.QThread):
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
p = re.findall((r"(?m)\[submodule\s*\"(?P<name>.*)\"\]\s*"
|
||||
p = re.findall((r'(?m)\[submodule\s*"(?P<name>.*)"\]\s*'
|
||||
r"path\s*=\s*(?P<path>.+)\s*"
|
||||
r"url\s*=\s*(?P<url>https?://.*)"), p)
|
||||
basedir = FreeCAD.getUserAppDataDir()
|
||||
@@ -124,7 +146,8 @@ class UpdateWorker(QtCore.QThread):
|
||||
state = 0
|
||||
repos.append([name, url, state])
|
||||
# querying custom addons
|
||||
customaddons = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons").GetString("CustomRepositories","").split("\n")
|
||||
customaddons = (FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
.GetString("CustomRepositories", "").split("\n"))
|
||||
for url in customaddons:
|
||||
if url:
|
||||
name = url.split("/")[-1]
|
||||
@@ -135,7 +158,7 @@ class UpdateWorker(QtCore.QThread):
|
||||
state = 0
|
||||
else:
|
||||
state = 1
|
||||
repos.append([name,url,state])
|
||||
repos.append([name, url, state])
|
||||
if not repos:
|
||||
self.info_label.emit(translate("AddonsInstaller", "Unable to download addon list."))
|
||||
else:
|
||||
@@ -170,7 +193,7 @@ class InfoWorker(QtCore.QThread):
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
desc = re.findall("<meta property=\"og:description\" content=\"(.*?)\"",p)
|
||||
desc = re.findall('<meta property="og:description" content="(.*?)"', p)
|
||||
if desc:
|
||||
desc = desc[0]
|
||||
else:
|
||||
@@ -188,19 +211,14 @@ class CheckWBWorker(QtCore.QThread):
|
||||
mark = QtCore.Signal(str)
|
||||
addon_repos = QtCore.Signal(object)
|
||||
|
||||
def __init__(self,repos):
|
||||
def __init__(self, repos):
|
||||
|
||||
QtCore.QThread.__init__(self)
|
||||
self.repos = repos
|
||||
|
||||
def run(self):
|
||||
|
||||
if NOGIT:
|
||||
self.stop = True
|
||||
return
|
||||
try:
|
||||
import git
|
||||
except:
|
||||
if NOGIT or not have_git:
|
||||
self.stop = True
|
||||
return
|
||||
basedir = FreeCAD.getUserAppDataDir()
|
||||
@@ -208,36 +226,40 @@ class CheckWBWorker(QtCore.QThread):
|
||||
upds = []
|
||||
gitpython_warning = False
|
||||
for repo in self.repos:
|
||||
if repo[2] == 1: #installed
|
||||
#print("Checking for updates for",repo[0])
|
||||
if repo[2] == 1: # installed
|
||||
# print("Checking for updates for", repo[0])
|
||||
clonedir = moddir + os.sep + repo[0]
|
||||
if os.path.exists(clonedir):
|
||||
self.repos[self.repos.index(repo)][2] = 2 # mark as already installed AND already checked for updates
|
||||
if not os.path.exists(clonedir + os.sep + '.git'):
|
||||
# mark as already installed AND already checked for updates
|
||||
self.repos[self.repos.index(repo)][2] = 2
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
# Repair addon installed with raw download
|
||||
bare_repo = git.Repo.clone_from(repo[1], clonedir + os.sep + '.git', bare=True)
|
||||
bare_repo = git.Repo.clone_from(repo[1], clonedir + os.sep + ".git", bare=True)
|
||||
try:
|
||||
with bare_repo.config_writer() as cw:
|
||||
cw.set('core', 'bare', False)
|
||||
cw.set("core", "bare", False)
|
||||
except AttributeError:
|
||||
if not gitpython_warning:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Outdated GitPython detected, consider upgrading with pip.")+"\n")
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"Outdated GitPython detected, "
|
||||
"consider upgrading with pip.") + "\n")
|
||||
gitpython_warning = True
|
||||
cw = bare_repo.config_writer()
|
||||
cw.set('core', 'bare', False)
|
||||
cw.set("core", "bare", False)
|
||||
del cw
|
||||
repo = git.Repo(clonedir)
|
||||
repo.head.reset('--hard')
|
||||
repo.head.reset("--hard")
|
||||
gitrepo = git.Git(clonedir)
|
||||
try:
|
||||
gitrepo.fetch()
|
||||
except:
|
||||
print("AddonManager: Unable to fetch git updates for repo",repo[0])
|
||||
except Exception:
|
||||
print("AddonManager: Unable to fetch git updates for repo", repo[0])
|
||||
else:
|
||||
if "git pull" in gitrepo.status():
|
||||
self.mark.emit(repo[0])
|
||||
upds.append(repo[0])
|
||||
self.repos[self.repos.index(repo)][2] = 3 # mark as already installed AND already checked for updates AND update available
|
||||
# mark as already installed AND already checked for updates AND update available
|
||||
self.repos[self.repos.index(repo)][2] = 3
|
||||
self.addon_repos.emit(self.repos)
|
||||
self.enable.emit(len(upds))
|
||||
self.stop = True
|
||||
@@ -257,49 +279,47 @@ class FillMacroListWorker(QtCore.QThread):
|
||||
self.macros = []
|
||||
|
||||
def run(self):
|
||||
|
||||
"""Populates the list of macros"""
|
||||
|
||||
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())]
|
||||
if self.macros:
|
||||
self.info_label_signal.emit(translate('AddonsInstaller', 'List of macros successfully retrieved.'))
|
||||
self.info_label_signal.emit(translate("AddonsInstaller", "List of macros successfully retrieved."))
|
||||
self.progressbar_show.emit(False)
|
||||
self.stop = True
|
||||
|
||||
def retrieve_macros_from_git(self):
|
||||
|
||||
"""Retrieve macros from FreeCAD-macros.git
|
||||
|
||||
Emits a signal for each macro in
|
||||
https://github.com/FreeCAD/FreeCAD-macros.git
|
||||
"""
|
||||
|
||||
try:
|
||||
import git
|
||||
except ImportError:
|
||||
if not have_git:
|
||||
self.info_label_signal.emit("GitPython not installed! Cannot retrieve macros from Git")
|
||||
FreeCAD.Console.PrintWarning(translate('AddonsInstaller', 'GitPython not installed! Cannot retrieve macros from git')+"\n")
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"GitPython not installed! Cannot retrieve macros from git")+"\n")
|
||||
return
|
||||
|
||||
self.info_label_signal.emit('Downloading list of macros from git...')
|
||||
self.info_label_signal.emit("Downloading list of macros from git...")
|
||||
try:
|
||||
git.Repo.clone_from('https://github.com/FreeCAD/FreeCAD-macros.git', self.repo_dir)
|
||||
except:
|
||||
FreeCAD.Console.PrintWarning(translate('AddonsInstaller', 'Something went wrong with the Git Macro Retrieval, possibly the Git executable is not in the path')+"\n")
|
||||
git.Repo.clone_from("https://github.com/FreeCAD/FreeCAD-macros.git", self.repo_dir)
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"Something went wrong with the Git Macro Retrieval, "
|
||||
"possibly the Git executable is not in the path") + "\n")
|
||||
for dirpath, _, filenames in os.walk(self.repo_dir):
|
||||
if '.git' in dirpath:
|
||||
continue
|
||||
for filename in filenames:
|
||||
if filename.lower().endswith('.fcmacro'):
|
||||
if ".git" in dirpath:
|
||||
continue
|
||||
for filename in filenames:
|
||||
if filename.lower().endswith(".fcmacro"):
|
||||
macro = Macro(filename[:-8]) # Remove ".FCMacro".
|
||||
macro.on_git = True
|
||||
macro.src_filename = os.path.join(dirpath, filename)
|
||||
self.macros.append(macro)
|
||||
|
||||
def retrieve_macros_from_wiki(self):
|
||||
|
||||
"""Retrieve macros from the wiki
|
||||
|
||||
Read the wiki and emit a signal for each found macro.
|
||||
@@ -310,18 +330,20 @@ class FillMacroListWorker(QtCore.QThread):
|
||||
self.progressbar_show.emit(True)
|
||||
u = utils.urlopen("https://www.freecadweb.org/wiki/Macros_recipes")
|
||||
if not u:
|
||||
FreeCAD.Console.PrintWarning(translate('AddonsInstaller', 'Appears to be an issue connecting to the Wiki, therefore cannot retrieve Wiki macro list at this time')+"\n")
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"Appears to be an issue connecting to the Wiki, "
|
||||
"therefore cannot retrieve Wiki macro list at this time") + "\n")
|
||||
return
|
||||
p = u.read()
|
||||
u.close()
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
macros = re.findall('title="(Macro.*?)"', p)
|
||||
macros = [mac for mac in macros if ('translated' not in mac)]
|
||||
macros = [mac for mac in macros if ("translated" not in mac)]
|
||||
for mac in macros:
|
||||
macname = mac[6:] # Remove "Macro ".
|
||||
macname = macname.replace("&","&")
|
||||
if (macname not in macros_blacklist) and ('recipes' not in macname.lower()):
|
||||
macname = macname.replace("&", "&")
|
||||
if (macname not in macros_blacklist) and ("recipes" not in macname.lower()):
|
||||
macro = Macro(macname)
|
||||
macro.on_wiki = True
|
||||
self.macros.append(macro)
|
||||
@@ -336,7 +358,7 @@ class ShowWorker(QtCore.QThread):
|
||||
|
||||
def __init__(self, repos, idx):
|
||||
|
||||
# repos is a list of [name,url,installbit,descr]
|
||||
# repos is a list of [name, url, installbit, descr]
|
||||
# name : Addon name
|
||||
# url : Addon repository location
|
||||
# installbit: 0 = Addon is not installed
|
||||
@@ -358,51 +380,54 @@ class ShowWorker(QtCore.QThread):
|
||||
else:
|
||||
u = None
|
||||
url = self.repos[self.idx][1]
|
||||
self.info_label.emit(translate("AddonsInstaller", "Retrieving info from") + ' ' + str(url))
|
||||
self.info_label.emit(translate("AddonsInstaller", "Retrieving info from") + " " + str(url))
|
||||
desc = ""
|
||||
regex = utils.getReadmeRegex(url)
|
||||
regex = utils.get_readme_regex(url)
|
||||
if regex:
|
||||
# extract readme from html via regex
|
||||
readmeurl = utils.getReadmeHTMLUrl(url)
|
||||
readmeurl = utils.get_readme_html_url(url)
|
||||
if not readmeurl:
|
||||
print("Debug: README not found for",url)
|
||||
print("Debug: README not found for", url)
|
||||
u = utils.urlopen(readmeurl)
|
||||
if not u:
|
||||
print("Debug: README not found at",readmeurl)
|
||||
print("Debug: README not found at", readmeurl)
|
||||
u = utils.urlopen(readmeurl)
|
||||
if u:
|
||||
p = u.read()
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
readme = re.findall(regex,p,flags=re.MULTILINE|re.DOTALL)
|
||||
readme = re.findall(regex, p, flags=re.MULTILINE | re.DOTALL)
|
||||
if readme:
|
||||
desc = readme[0]
|
||||
else:
|
||||
print("Debug: README not found at",readmeurl)
|
||||
print("Debug: README not found at", readmeurl)
|
||||
else:
|
||||
# convert raw markdown using lib
|
||||
readmeurl = utils.getReadmeUrl(url)
|
||||
readmeurl = utils.get_readme_url(url)
|
||||
if not readmeurl:
|
||||
print("Debug: README not found for",url)
|
||||
print("Debug: README not found for", url)
|
||||
u = utils.urlopen(readmeurl)
|
||||
if u:
|
||||
p = u.read()
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
desc = utils.fixRelativeLinks(p,readmeurl.rsplit("/README.md")[0])
|
||||
try:
|
||||
if NOMARKDOWN:
|
||||
raise ImportError
|
||||
import markdown # try to use system Markdown lib
|
||||
desc = markdown.markdown(desc,extensions=['md_in_html'])
|
||||
except ImportError:
|
||||
message = " <div style=\"width: 100%; text-align:center; background: #91bbe0;\"><strong style=\"color: #FFFFFF;\">"+translate("AddonsInstaller","Raw markdown displayed")+"</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller","Python Markdown library is missing.")+"<br/></div><hr/><pre>" + desc + "</pre>"
|
||||
desc = utils.fix_relative_links(p, readmeurl.rsplit("/README.md")[0])
|
||||
if NOMARKDOWN or not have_markdown:
|
||||
desc = markdown.markdown(desc, extensions=["md_in_html"])
|
||||
else:
|
||||
message = """
|
||||
<div style="width: 100%; text-align:center;background: #91bbe0;">
|
||||
<strong style="color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "Raw markdown displayed")
|
||||
message += "</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller", "Python Markdown library is missing.")
|
||||
message += "<br/></div><hr/><pre>" + desc + "</pre>"
|
||||
desc = message
|
||||
else:
|
||||
print("Debug: README not found at",readmeurl)
|
||||
print("Debug: README not found at", readmeurl)
|
||||
if desc == "":
|
||||
# fall back to the description text
|
||||
u = utils.urlopen(url)
|
||||
@@ -414,9 +439,9 @@ class ShowWorker(QtCore.QThread):
|
||||
if sys.version_info.major >= 3 and isinstance(p, bytes):
|
||||
p = p.decode("utf-8")
|
||||
u.close()
|
||||
descregex = utils.getDescRegex(url)
|
||||
descregex = utils.get_desc_regex(url)
|
||||
if descregex:
|
||||
desc = re.findall(descregex,p)
|
||||
desc = re.findall(descregex, p)
|
||||
if desc:
|
||||
desc = desc[0]
|
||||
if not desc:
|
||||
@@ -427,92 +452,133 @@ class ShowWorker(QtCore.QThread):
|
||||
if self.repos[self.idx][2] == 1:
|
||||
upd = False
|
||||
# checking for updates
|
||||
if not NOGIT:
|
||||
try:
|
||||
import git
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
repo = self.repos[self.idx]
|
||||
clonedir = FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + repo[0]
|
||||
if os.path.exists(clonedir):
|
||||
if not os.path.exists(clonedir + os.sep + '.git'):
|
||||
# Repair addon installed with raw download
|
||||
bare_repo = git.Repo.clone_from(repo[1], clonedir + os.sep + '.git', bare=True)
|
||||
try:
|
||||
with bare_repo.config_writer() as cw:
|
||||
cw.set('core', 'bare', False)
|
||||
except AttributeError:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Outdated GitPython detected, consider upgrading with pip.")+"\n")
|
||||
cw = bare_repo.config_writer()
|
||||
cw.set('core', 'bare', False)
|
||||
del cw
|
||||
repo = git.Repo(clonedir)
|
||||
repo.head.reset('--hard')
|
||||
gitrepo = git.Git(clonedir)
|
||||
gitrepo.fetch()
|
||||
if "git pull" in gitrepo.status():
|
||||
upd = True
|
||||
if not NOGIT and have_git:
|
||||
repo = self.repos[self.idx]
|
||||
clonedir = FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + repo[0]
|
||||
if os.path.exists(clonedir):
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
# Repair addon installed with raw download
|
||||
bare_repo = git.Repo.clone_from(repo[1], clonedir + os.sep + ".git", bare=True)
|
||||
try:
|
||||
with bare_repo.config_writer() as cw:
|
||||
cw.set("core", "bare", False)
|
||||
except AttributeError:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"Outdated GitPython detected, "
|
||||
"consider upgrading with pip.") + "\n")
|
||||
cw = bare_repo.config_writer()
|
||||
cw.set("core", "bare", False)
|
||||
del cw
|
||||
repo = git.Repo(clonedir)
|
||||
repo.head.reset("--hard")
|
||||
gitrepo = git.Git(clonedir)
|
||||
gitrepo.fetch()
|
||||
if "git pull" in gitrepo.status():
|
||||
upd = True
|
||||
# If there is an update pending, lets user know via the UI
|
||||
if upd:
|
||||
message = "<div style=\"width: 100%;text-align: center;background: #75AFFD;\"><br/><strong style=\"background: #397FF7;color: #FFFFFF;\">" + translate("AddonsInstaller", "An update is available for this addon.")
|
||||
message += "</strong><br/></div><hr/>" + desc + '<br/><br/>Addon repository: <a href="' + self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
self.repos[self.idx][2] = 3 # mark as already installed AND already checked for updates AND update is available
|
||||
message = """
|
||||
<div style="width: 100%;text-align: center;background: #75AFFD;">
|
||||
<br/>
|
||||
<strong style="background: #397FF7;color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "An update is available for this addon.")
|
||||
message += "</strong><br/></div><hr/>" + desc + '<br/><br/>Addon repository: <a href="'
|
||||
message += self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + "</a>"
|
||||
# mark as already installed AND already checked for updates AND update is available
|
||||
self.repos[self.idx][2] = 3
|
||||
# If there isn't, indicate that this addon is already installed
|
||||
else:
|
||||
message = "<div style=\"width: 100%;text-align: center;background: #C1FEB2;\"><br/><strong style=\"background: #00B629;color: #FFFFFF;\">" + translate("AddonsInstaller", "This addon is already installed.") + "</strong><br/></div><hr/>"
|
||||
message += desc + '<br/><br/>Addon repository: <a href="' + self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
self.repos[self.idx][2] = 2 # mark as already installed AND already checked for updates
|
||||
message = """
|
||||
<div style="width: 100%;text-align: center;background: #C1FEB2;">
|
||||
<br/>
|
||||
<strong style="background: #00B629;color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "This addon is already installed.")
|
||||
message += "</strong><br/></div><hr/>" + desc
|
||||
message += '<br/><br/>Addon repository: <a href="'
|
||||
message += self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + "</a>"
|
||||
self.repos[self.idx][2] = 2 # mark as already installed AND already checked for updates
|
||||
# Let the user know the install path for this addon
|
||||
message += '<br/>' + translate("AddonInstaller","Installed location")+": "+ FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
message += "<br/>" + translate("AddonInstaller", "Installed location") + ": "
|
||||
message += FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
self.addon_repos.emit(self.repos)
|
||||
elif self.repos[self.idx][2] == 2:
|
||||
message = "<div style=\"width: 100%;text-align: center;background: #C1FEB2;\"><br/><strong style=\"background: #00B629;color: #FFFFFF;\">" + translate("AddonsInstaller", "This addon is already installed.") + "</strong><br></div><hr/>"
|
||||
message += desc + '<br/><br/>Addon repository: <a href="' + self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
message += '<br/>' + translate("AddonInstaller","Installed location")+": "+ FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
message = """
|
||||
<div style="width: 100%;text-align: center;background: #C1FEB2;">
|
||||
<br/>
|
||||
<strong style="background: #00B629;color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "This addon is already installed.")
|
||||
message += "</strong><br></div><hr/>" + desc
|
||||
message += '<br/><br/>Addon repository: <a href="'
|
||||
message += self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + "</a>"
|
||||
message += "<br/>" + translate("AddonInstaller", "Installed location") + ": "
|
||||
message += FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
elif self.repos[self.idx][2] == 3:
|
||||
message = "<div style=\"width: 100%;text-align: center;background: #75AFFD;\"><br/><strong style=\"background: #397FF7;color: #FFFFFF;\">" + translate("AddonsInstaller", "An update is available for this addon.")
|
||||
message += "</strong><br/></div><hr/>" + desc + '<br/><br/>Addon repository: <a href="' + self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
message += '<br/>' + translate("AddonInstaller","Installed location")+": "+ FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
message = """
|
||||
<div style="width: 100%;text-align: center;background: #75AFFD;">
|
||||
<br/>
|
||||
<strong style="background: #397FF7;color: #FFFFFF;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "An update is available for this addon.")
|
||||
message += "</strong><br/></div><hr/>" + desc + '<br/><br/>Addon repository: <a href="'
|
||||
message += self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + "</a>"
|
||||
message += "<br/>" + translate("AddonInstaller", "Installed location") + ": "
|
||||
message += FreeCAD.getUserAppDataDir() + os.sep + "Mod" + os.sep + self.repos[self.idx][0]
|
||||
else:
|
||||
message = desc + '<br/><br/>Addon repository: <a href="' + self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
message = desc + '<br/><br/>Addon repository: <a href="'
|
||||
message += self.repos[self.idx][1] + '">' + self.repos[self.idx][1] + '</a>'
|
||||
|
||||
# If the Addon is obsolete, let the user know through the Addon UI
|
||||
if self.repos[self.idx][0] in obsolete:
|
||||
message = " <div style=\"width: 100%; text-align:center; background: #FFB3B3;\"><strong style=\"color: #FFFFFF; background: #FF0000;\">"+translate("AddonsInstaller","This addon is marked as obsolete")+"</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller","This usually means it is no longer maintained, and some more advanced addon in this list provides the same functionality.")+"<br/></div><hr/>" + desc
|
||||
message = """
|
||||
<div style="width: 100%; text-align:center; background: #FFB3B3;">
|
||||
<strong style="color: #FFFFFF; background: #FF0000;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "This addon is marked as obsolete") + "</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller",
|
||||
"This usually means it is no longer maintained, "
|
||||
"and some more advanced addon in this list "
|
||||
"provides the same functionality.") + "<br/></div><hr/>" + desc
|
||||
|
||||
# If the Addon is Python 2 only, let the user know through the Addon UI
|
||||
if self.repos[self.idx][0] in py2only:
|
||||
message = " <div style=\"width: 100%; text-align:center; background: #ffe9b3;\"><strong style=\"color: #FFFFFF; background: #ff8000;\">"+translate("AddonsInstaller","This addon is marked as Python 2 Only")+"</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller","This workbench may no longer be maintained and installing it on a Python 3 system will more than likely result in errors at startup or while in use.")+"<br/></div><hr/>" + desc
|
||||
message = """
|
||||
<div style="width: 100%; text-align:center; background: #ffe9b3;">
|
||||
<strong style="color: #FFFFFF; background: #ff8000;">
|
||||
"""
|
||||
message += translate("AddonsInstaller", "This addon is marked as Python 2 Only") + "</strong><br/><br/>"
|
||||
message += translate("AddonsInstaller",
|
||||
"This workbench may no longer be maintained and "
|
||||
"installing it on a Python 3 system will more than "
|
||||
"likely result in errors at startup or while in use.")
|
||||
message += "<br/></div><hr/>" + desc
|
||||
|
||||
self.info_label.emit( message )
|
||||
self.info_label.emit(message)
|
||||
self.progressbar_show.emit(False)
|
||||
self.mustLoadImages = True
|
||||
l = self.loadImages( message, self.repos[self.idx][1], self.repos[self.idx][0])
|
||||
if l:
|
||||
self.info_label.emit( l )
|
||||
label = self.loadImages(message, self.repos[self.idx][1], self.repos[self.idx][0])
|
||||
if label:
|
||||
self.info_label.emit(label)
|
||||
self.stop = True
|
||||
|
||||
def stopImageLoading(self):
|
||||
|
||||
"this stops the image loading process and allow the thread to terminate earlier"
|
||||
|
||||
self.mustLoadImages = False
|
||||
|
||||
def loadImages(self,message,url,wbName):
|
||||
|
||||
def loadImages(self, message, url, wbName):
|
||||
"checks if the given page contains images and downloads them"
|
||||
|
||||
# QTextBrowser cannot display online images. So we download them
|
||||
# here, and replace the image link in the html code with the
|
||||
# downloaded version
|
||||
|
||||
imagepaths = re.findall("<img.*?src=\"(.*?)\"",message)
|
||||
imagepaths = re.findall("<img.*?src=\"(.*?)\"", message)
|
||||
if imagepaths:
|
||||
storedimages = []
|
||||
store = os.path.join(FreeCAD.getUserAppDataDir(),"AddonManager","Images")
|
||||
store = os.path.join(FreeCAD.getUserAppDataDir(), "AddonManager", "Images")
|
||||
if not os.path.exists(store):
|
||||
os.makedirs(store)
|
||||
for path in imagepaths:
|
||||
@@ -526,38 +592,37 @@ class ShowWorker(QtCore.QThread):
|
||||
path = utils.getserver(url) + path
|
||||
name = path.split("/")[-1]
|
||||
if name and path.startswith("http"):
|
||||
storename = os.path.join(store,name)
|
||||
storename = os.path.join(store, name)
|
||||
if len(storename) >= 260:
|
||||
remainChars = 259 - (len(store) + len(wbName) + 1)
|
||||
storename = os.path.join(store,wbName+name[-remainChars:])
|
||||
storename = os.path.join(store, wbName+name[-remainChars:])
|
||||
if not os.path.exists(storename):
|
||||
try:
|
||||
u = utils.urlopen(path)
|
||||
imagedata = u.read()
|
||||
u.close()
|
||||
except:
|
||||
print("AddonManager: Debug: Error retrieving image from",path)
|
||||
except Exception:
|
||||
print("AddonManager: Debug: Error retrieving image from", path)
|
||||
else:
|
||||
f = open(storename,"wb")
|
||||
f = open(storename, "wb")
|
||||
f.write(imagedata)
|
||||
f.close()
|
||||
|
||||
# resize the image to 300x300px if needed
|
||||
from PySide import QtCore,QtGui
|
||||
img = QtGui.QImage(storename)
|
||||
if (img.width() > 300) or (img.height() > 300):
|
||||
pix = QtGui.QPixmap()
|
||||
pix = pix.fromImage(img.scaled(300,300,QtCore.Qt.KeepAspectRatio,QtCore.Qt.FastTransformation))
|
||||
pix.save(storename, "jpeg",100)
|
||||
message = message.replace("src=\""+origpath,"src=\"file:///"+storename.replace("\\","/"))
|
||||
#print(message)
|
||||
pix = pix.fromImage(img.scaled(300, 300,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.FastTransformation))
|
||||
pix.save(storename, "jpeg", 100)
|
||||
message = message.replace("src=\""+origpath, "src=\"file:///"+storename.replace("\\", "/"))
|
||||
# print(message)
|
||||
return message
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class GetMacroDetailsWorker(QtCore.QThread):
|
||||
|
||||
"""Retrieve the macro details for a macro"""
|
||||
|
||||
info_label = QtCore.Signal(str)
|
||||
@@ -573,36 +638,35 @@ class GetMacroDetailsWorker(QtCore.QThread):
|
||||
self.progressbar_show.emit(True)
|
||||
self.info_label.emit(translate("AddonsInstaller", "Retrieving description..."))
|
||||
if not self.macro.parsed and self.macro.on_git:
|
||||
self.info_label.emit(translate('AddonsInstaller', 'Retrieving info from git'))
|
||||
self.info_label.emit(translate("AddonsInstaller", "Retrieving info from git"))
|
||||
self.macro.fill_details_from_file(self.macro.src_filename)
|
||||
if not self.macro.parsed and self.macro.on_wiki:
|
||||
self.info_label.emit(translate('AddonsInstaller', 'Retrieving info from wiki'))
|
||||
mac = self.macro.name.replace(' ', '_')
|
||||
mac = mac.replace('&', '%26')
|
||||
mac = mac.replace('+', '%2B')
|
||||
url = 'https://www.freecadweb.org/wiki/Macro_' + mac
|
||||
self.info_label.emit(translate("AddonsInstaller", "Retrieving info from wiki"))
|
||||
mac = self.macro.name.replace(" ", "_")
|
||||
mac = mac.replace("&", "%26")
|
||||
mac = mac.replace("+", "%2B")
|
||||
url = "https://www.freecadweb.org/wiki/Macro_" + mac
|
||||
self.macro.fill_details_from_wiki(url)
|
||||
if self.macro.is_installed():
|
||||
already_installed_msg = ('<strong style=\"background: #00B629;\">'
|
||||
+ translate("AddonsInstaller", "This macro is already installed.")
|
||||
+ '</strong><br>')
|
||||
+ translate("AddonsInstaller", "This macro is already installed.")
|
||||
+ '</strong><br>')
|
||||
else:
|
||||
already_installed_msg = ''
|
||||
already_installed_msg = ""
|
||||
message = (already_installed_msg
|
||||
+ "<h1>"+self.macro.name+"</h1>"
|
||||
+ self.macro.desc
|
||||
+ '<br/><br/>Macro location: <a href="'
|
||||
+ self.macro.url
|
||||
+ '">'
|
||||
+ self.macro.url
|
||||
+ '</a>')
|
||||
+ "<h1>"+self.macro.name+"</h1>"
|
||||
+ self.macro.desc
|
||||
+ "<br/><br/>Macro location: <a href=\""
|
||||
+ self.macro.url
|
||||
+ "\">"
|
||||
+ self.macro.url
|
||||
+ "</a>")
|
||||
self.info_label.emit(message)
|
||||
self.progressbar_show.emit(False)
|
||||
self.stop = True
|
||||
|
||||
|
||||
class InstallWorker(QtCore.QThread):
|
||||
|
||||
"This worker installs a workbench"
|
||||
|
||||
info_label = QtCore.Signal(str)
|
||||
@@ -616,35 +680,26 @@ class InstallWorker(QtCore.QThread):
|
||||
self.repos = repos
|
||||
|
||||
def run(self):
|
||||
|
||||
"installs or updates the selected addon"
|
||||
|
||||
git = None
|
||||
try:
|
||||
import git
|
||||
except Exception as e:
|
||||
if not have_git:
|
||||
self.info_label.emit("GitPython not found.")
|
||||
print(e)
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller","GitPython not found. Using standard download instead.")+"\n")
|
||||
try:
|
||||
import zipfile
|
||||
except:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"GitPython not found. Using standard download instead.") + "\n")
|
||||
if not have_zip:
|
||||
self.info_label.emit("no zip support.")
|
||||
FreeCAD.Console.PrintError(translate("AddonsInstaller","Your version of python doesn't appear to support ZIP files. Unable to proceed.")+"\n")
|
||||
FreeCAD.Console.PrintError(translate("AddonsInstaller",
|
||||
"Your version of python doesn't appear to support ZIP "
|
||||
"files. Unable to proceed.") + "\n")
|
||||
return
|
||||
try:
|
||||
import StringIO as io
|
||||
except ImportError: # StringIO is not available with python3
|
||||
import io
|
||||
if not isinstance(self.idx,list):
|
||||
|
||||
if not isinstance(self.idx, list):
|
||||
self.idx = [self.idx]
|
||||
for idx in self.idx:
|
||||
if idx < 0:
|
||||
return
|
||||
if not self.repos:
|
||||
return
|
||||
if NOGIT:
|
||||
git = None
|
||||
basedir = FreeCAD.getUserAppDataDir()
|
||||
moddir = basedir + os.sep + "Mod"
|
||||
if not os.path.exists(moddir):
|
||||
@@ -654,26 +709,33 @@ class InstallWorker(QtCore.QThread):
|
||||
if os.path.exists(clonedir):
|
||||
self.info_label.emit("Updating module...")
|
||||
if sys.version_info.major > 2 and str(self.repos[idx][0]) in py2only:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "User requested updating a Python 2 workbench on a system running Python 3 - ")+str(self.repos[idx][0])+"\n")
|
||||
if git:
|
||||
if not os.path.exists(clonedir + os.sep + '.git'):
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"User requested updating a Python 2 workbench on "
|
||||
"a system running Python 3 - ") +
|
||||
str(self.repos[idx][0])+"\n")
|
||||
if have_git:
|
||||
if not os.path.exists(clonedir + os.sep + ".git"):
|
||||
# Repair addon installed with raw download
|
||||
bare_repo = git.Repo.clone_from(self.repos[idx][1], clonedir + os.sep + '.git', bare=True)
|
||||
bare_repo = git.Repo.clone_from(self.repos[idx][1], clonedir + os.sep + ".git", bare=True)
|
||||
try:
|
||||
with bare_repo.config_writer() as cw:
|
||||
cw.set('core', 'bare', False)
|
||||
cw.set("core", "bare", False)
|
||||
except AttributeError:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "Outdated GitPython detected, consider upgrading with pip.")+"\n")
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"Outdated GitPython detected, consider "
|
||||
"upgrading with pip.") + "\n")
|
||||
cw = bare_repo.config_writer()
|
||||
cw.set('core', 'bare', False)
|
||||
cw.set("core", "bare", False)
|
||||
del cw
|
||||
repo = git.Repo(clonedir)
|
||||
repo.head.reset('--hard')
|
||||
repo.head.reset("--hard")
|
||||
repo = git.Git(clonedir)
|
||||
try:
|
||||
answer = repo.pull() + "\n\n" + translate("AddonsInstaller", "Workbench successfully updated. Please restart FreeCAD to apply the changes.")
|
||||
except:
|
||||
print("Error updating module",self.repos[idx][1]," - Please fix manually")
|
||||
answer = repo.pull() + "\n\n" + translate("AddonsInstaller",
|
||||
"Workbench successfully updated. "
|
||||
"Please restart FreeCAD to apply the changes.")
|
||||
except Exception:
|
||||
print("Error updating module", self.repos[idx][1], " - Please fix manually")
|
||||
answer = repo.status()
|
||||
print(answer)
|
||||
else:
|
||||
@@ -682,24 +744,32 @@ class InstallWorker(QtCore.QThread):
|
||||
for submodule in repo_sms.submodules:
|
||||
submodule.update(init=True, recursive=True)
|
||||
else:
|
||||
answer = self.download(self.repos[idx][1],clonedir) + "\n\n" + translate("AddonsInstaller", "Workbench successfully updated. Please restart FreeCAD to apply the changes.")
|
||||
answer = self.download(self.repos[idx][1], clonedir) + "\n\n"
|
||||
answer += translate("AddonsInstaller",
|
||||
"Workbench successfully updated. Please "
|
||||
"restart FreeCAD to apply the changes.")
|
||||
else:
|
||||
self.info_label.emit("Checking module dependencies...")
|
||||
depsok,answer = self.checkDependencies(self.repos[idx][1])
|
||||
depsok, answer = self.check_dependencies(self.repos[idx][1])
|
||||
if depsok:
|
||||
if sys.version_info.major > 2 and str(self.repos[idx][0]) in py2only:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller", "User requested installing a Python 2 workbench on a system running Python 3 - ")+str(self.repos[idx][0])+"\n")
|
||||
if git:
|
||||
FreeCAD.Console.PrintWarning(translate("AddonsInstaller",
|
||||
"User requested installing a Python 2 "
|
||||
"workbench on a system running Python 3 - ") +
|
||||
str(self.repos[idx][0]) + "\n")
|
||||
if have_git:
|
||||
self.info_label.emit("Cloning module...")
|
||||
repo = git.Repo.clone_from(self.repos[idx][1], clonedir, branch='master')
|
||||
repo = git.Repo.clone_from(self.repos[idx][1], clonedir, branch="master")
|
||||
|
||||
# Make sure to clone all the submodules as well
|
||||
if repo.submodules:
|
||||
repo.submodule_update(recursive=True)
|
||||
else:
|
||||
self.info_label.emit("Downloading module...")
|
||||
self.download(self.repos[idx][1],clonedir)
|
||||
answer = translate("AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.")
|
||||
self.download(self.repos[idx][1], clonedir)
|
||||
answer = translate("AddonsInstaller",
|
||||
"Workbench successfully installed. Please restart "
|
||||
"FreeCAD to apply the changes.")
|
||||
# symlink any macro contained in the module to the macros folder
|
||||
macro_dir = FreeCAD.getUserMacroDir(True)
|
||||
if not os.path.exists(macro_dir):
|
||||
@@ -707,24 +777,25 @@ class InstallWorker(QtCore.QThread):
|
||||
if os.path.exists(clonedir):
|
||||
for f in os.listdir(clonedir):
|
||||
if f.lower().endswith(".fcmacro"):
|
||||
print("copying macro:",f)
|
||||
print("copying macro:", f)
|
||||
utils.symlink(os.path.join(clonedir, f), os.path.join(macro_dir, f))
|
||||
FreeCAD.ParamGet('User parameter:Plugins/'+self.repos[idx][0]).SetString("destination",clonedir)
|
||||
answer += "\n\n"+translate("AddonsInstaller", "A macro has been installed and is available under Macro -> Macros menu")+":"
|
||||
answer += "\n<b>" + f + "</b>"
|
||||
FreeCAD.ParamGet('User parameter:Plugins/' +
|
||||
self.repos[idx][0]).SetString("destination", clonedir)
|
||||
answer += "\n\n" + translate("AddonsInstaller",
|
||||
"A macro has been installed and is available "
|
||||
"under Macro -> Macros menu")
|
||||
answer += ":\n<b>" + f + "</b>"
|
||||
self.progressbar_show.emit(False)
|
||||
self.info_label.emit(answer)
|
||||
self.mark_recompute.emit(self.repos[idx][0])
|
||||
self.stop = True
|
||||
|
||||
def checkDependencies(self,baseurl):
|
||||
|
||||
def check_dependencies(self, baseurl):
|
||||
"checks if the repo contains a metadata.txt and check its contents"
|
||||
|
||||
import FreeCADGui
|
||||
ok = True
|
||||
message = ""
|
||||
depsurl = baseurl.replace("github.com","raw.githubusercontent.com")
|
||||
depsurl = baseurl.replace("github.com", "raw.githubusercontent.com")
|
||||
if not depsurl.endswith("/"):
|
||||
depsurl += "/"
|
||||
depsurl += "master/metadata.txt"
|
||||
@@ -736,74 +807,69 @@ class InstallWorker(QtCore.QThread):
|
||||
|
||||
# urllib2 gives us a bytelike object instead of a string. Have to consider that
|
||||
try:
|
||||
depsfile = depsfile.decode('utf-8')
|
||||
depsfile = depsfile.decode("utf-8")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
deps = depsfile.split("\n")
|
||||
for l in deps:
|
||||
if l.startswith("workbenches="):
|
||||
depswb = l.split("=")[1].split(",")
|
||||
for line in deps:
|
||||
if line.startswith("workbenches="):
|
||||
depswb = line.split("=")[1].split(",")
|
||||
for wb in depswb:
|
||||
if wb.strip():
|
||||
if not wb.strip() in FreeCADGui.listWorkbenches().keys():
|
||||
if not wb.strip()+"Workbench" in FreeCADGui.listWorkbenches().keys():
|
||||
ok = False
|
||||
message += translate("AddonsInstaller","Missing workbench") + ": " + wb + ", "
|
||||
elif l.startswith("pylibs="):
|
||||
depspy = l.split("=")[1].split(",")
|
||||
message += translate("AddonsInstaller", "Missing workbench") + ": " + wb + ", "
|
||||
elif line.startswith("pylibs="):
|
||||
depspy = line.split("=")[1].split(",")
|
||||
for pl in depspy:
|
||||
if pl.strip():
|
||||
try:
|
||||
__import__(pl.strip())
|
||||
except:
|
||||
except ImportError:
|
||||
ok = False
|
||||
message += translate("AddonsInstaller","Missing python module") +": " + pl + ", "
|
||||
elif l.startswith("optionalpylibs="):
|
||||
opspy = l.split("=")[1].split(",")
|
||||
message += translate("AddonsInstaller", "Missing python module") + ": " + pl + ", "
|
||||
elif line.startswith("optionalpylibs="):
|
||||
opspy = line.split("=")[1].split(",")
|
||||
for pl in opspy:
|
||||
if pl.strip():
|
||||
try:
|
||||
__import__(pl.strip())
|
||||
except:
|
||||
message += translate("AddonsInstaller","Missing optional python module (doesn't prevent installing)") +": " + pl + ", "
|
||||
except ImportError:
|
||||
message += translate("AddonsInstaller",
|
||||
"Missing optional python module (doesn't prevent installing)")
|
||||
message += ": " + pl + ", "
|
||||
if message and (not ok):
|
||||
message = translate("AddonsInstaller", "Some errors were found that prevent to install this workbench") + ": <b>" + message + "</b>. "
|
||||
message += translate("AddonsInstaller","Please install the missing components first.")
|
||||
message = translate("AddonsInstaller", "Some errors were found that prevent to install this workbench")
|
||||
message += ": <b>" + message + "</b>. "
|
||||
message += translate("AddonsInstaller", "Please install the missing components first.")
|
||||
return ok, message
|
||||
|
||||
def download(self,baseurl,clonedir):
|
||||
|
||||
def download(self, baseurl, clonedir):
|
||||
"downloads and unzip a zip version from a git repo"
|
||||
|
||||
import zipfile
|
||||
bakdir = None
|
||||
if os.path.exists(clonedir):
|
||||
bakdir = clonedir+".bak"
|
||||
if os.path.exists(bakdir):
|
||||
shutil.rmtree(bakdir)
|
||||
os.rename(clonedir,bakdir)
|
||||
os.rename(clonedir, bakdir)
|
||||
os.makedirs(clonedir)
|
||||
zipurl = utils.getZipUrl(baseurl)
|
||||
zipurl = utils.get_zip_url(baseurl)
|
||||
if not zipurl:
|
||||
return translate("AddonsInstaller", "Error: Unable to locate zip from") + " " + baseurl
|
||||
try:
|
||||
print("Downloading "+zipurl)
|
||||
u = utils.urlopen(zipurl)
|
||||
except:
|
||||
except Exception:
|
||||
return translate("AddonsInstaller", "Error: Unable to download") + " " + zipurl
|
||||
if not u:
|
||||
return translate("AddonsInstaller", "Error: Unable to download") + " " + zipurl
|
||||
if sys.version_info.major < 3:
|
||||
import StringIO as io
|
||||
_stringio = io.StringIO
|
||||
else:
|
||||
import io
|
||||
_stringio = io.BytesIO
|
||||
zfile = _stringio()
|
||||
zfile.write(u.read())
|
||||
zfile = zipfile.ZipFile(zfile)
|
||||
master = zfile.namelist()[0] # github will put everything in a subfolder
|
||||
master = zfile.namelist()[0] # github will put everything in a subfolder
|
||||
zfile.extractall(clonedir)
|
||||
u.close()
|
||||
zfile.close()
|
||||
@@ -816,7 +882,6 @@ class InstallWorker(QtCore.QThread):
|
||||
|
||||
|
||||
class CheckSingleWorker(QtCore.QThread):
|
||||
|
||||
"""Worker to check for updates for a single addon"""
|
||||
|
||||
updateAvailable = QtCore.Signal(bool)
|
||||
@@ -828,21 +893,21 @@ class CheckSingleWorker(QtCore.QThread):
|
||||
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
import git
|
||||
except:
|
||||
if not have_git:
|
||||
return
|
||||
FreeCAD.Console.PrintLog("Checking for available updates of the "+self.name+" addon\n")
|
||||
addondir = os.path.join(FreeCAD.getUserAppDataDir(),"Mod",self.name)
|
||||
addondir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", self.name)
|
||||
if os.path.exists(addondir):
|
||||
if os.path.exists(addondir + os.sep + '.git'):
|
||||
if os.path.exists(addondir + os.sep + ".git"):
|
||||
gitrepo = git.Git(addondir)
|
||||
try:
|
||||
gitrepo.fetch()
|
||||
if "git pull" in gitrepo.status():
|
||||
self.updateAvailable.emit(True)
|
||||
return
|
||||
except:
|
||||
except Exception:
|
||||
# can fail for any number of reasons, ex. not being online
|
||||
pass
|
||||
self.updateAvailable.emit(False)
|
||||
|
||||
# @}
|
||||
|
||||
Reference in New Issue
Block a user