#!/usr/bin/env python
# -*- coding: utf-8 -*-

#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net>              *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

from __future__ import print_function

__title__="FreeCAD Addons installer Macro"
__author__ = "Yorik van Havre","Jonathan Wiedemann"
__url__ = "http://www.freecadweb.org"

'''
FreeCAD Addons installer macro

INSTALLATION

This script is made to be used as a FreeCAD macro, and to be placed
inside your macros folder (default is $HOME/.FreeCAD on mac/linux,
C:/Users/youruser/Application Data/FreeCAD on windows).

It will fetch its contents from https://github.com/FreeCAD/FreeCAD-addons
You need a working internet connection, and the python-git package
installed.
'''

from PySide import QtCore, QtGui
import FreeCAD,urllib2,re,os,shutil,sys

import ssl # for ssl certificates when downloading addons from git

NOGIT = False # for debugging purposes, set this to True to always use http downloads

MACROS_BLACKLIST = ["BOLTS","WorkFeatures","how to install","PartsLibrary"]

def symlink(source, link_name):
    if os.path.exists(link_name):
        print("symlink already exists")
    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
            flags = 1 if os.path.isdir(source) else 0
            if csl(link_name, source, flags) == 0:
                raise ctypes.WinError()

# Wrapper for urllib2.urlopen - python 2.7.8 does not yet support ssl.create_default_context
def openUrl(url):
    if sys.version_info < (2, 7, 9):
        return urllib2.urlopen(url)
    else:
        ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        return urllib2.urlopen(url, context=ctx)

class ProxyPanel(QtGui.QWidget):

    # Emit this signal when the user clicks the "Save & Close" button
    saveAndClose = QtCore.Signal()

    def __init__(self):
        QtGui.QWidget.__init__(self)

        # Create primary UI objects
        buttonGroup = QtGui.QButtonGroup()
        self.noProxy = QtGui.QRadioButton()
        self.systemProxy = QtGui.QRadioButton()
        self.userProxy = QtGui.QRadioButton()

        buttonGroup.addButton(self.noProxy)
        buttonGroup.addButton(self.systemProxy)
        buttonGroup.addButton(self.userProxy)

        self.proxyUrlEdit = QtGui.QLineEdit()

        # Initialize button group and text field
        proxyUrl = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetString('proxyUrl', '')
        self.proxyUrlEdit.setText(proxyUrl)

        proxyType = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetString('proxyType', 'none')
        if proxyType == 'system':
            self.systemProxy.setChecked(True)
            self.proxyUrlEdit.setEnabled(False)
        elif proxyType == 'user':
            self.userProxy.setChecked(True)
            self.proxyUrlEdit.setEnabled(True)
        else:
            self.noProxy.setChecked(True)
            self.proxyUrlEdit.setEnabled(False)

        # Assemble the proxy page UI
        self.urlEditLayout = QtGui.QHBoxLayout()
        self.urlEditLabel = QtGui.QLabel()
        self.urlEditLayout.addWidget(self.urlEditLabel)
        self.urlEditLayout.addWidget(self.proxyUrlEdit)
        self.urlEditWidget = QtGui.QWidget()
        self.urlEditWidget.setLayout(self.urlEditLayout)

        self.buttonLayout = QtGui.QHBoxLayout()
        self.buttonLayout.addSpacerItem(QtGui.QSpacerItem(1, 1, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed))
        self.buttonApply = QtGui.QPushButton()
        self.buttonLayout.addWidget(self.buttonApply)
        self.buttonWidget = QtGui.QWidget()
        self.buttonWidget.setLayout(self.buttonLayout)

        self.mainLayout = QtGui.QVBoxLayout()
        self.mainLayout.addWidget(self.noProxy)
        self.mainLayout.addWidget(self.systemProxy)
        self.mainLayout.addWidget(self.userProxy)
        self.mainLayout.addWidget(self.urlEditWidget)
        self.mainLayout.addWidget(self.buttonWidget)
        self.mainLayout.addSpacerItem(QtGui.QSpacerItem(1, 1, QtGui.QSizePolicy.Fixed,  QtGui.QSizePolicy.Expanding))
        self.setLayout(self.mainLayout)

        self.retranslateUi()

        self.noProxy.toggled.connect(self.toggleSystemProxy)
        self.systemProxy.toggled.connect(self.toggleSystemProxy)
        self.userProxy.toggled.connect(self.toggleSystemProxy)
        self.buttonApply.clicked.connect(self.applySettings)

    def toggleSystemProxy(self, checkState):
        if checkState:
            if self.userProxy.isChecked():
                self.proxyUrlEdit.setEnabled(True)
            else:
                self.proxyUrlEdit.setEnabled(False)

    def applySettings(self):
        if self.userProxy.isChecked():
            FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetString('proxyType', 'user')
        elif self.noProxy.isChecked():
            FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetString('proxyType', 'none')
        elif self.systemProxy.isChecked():
            FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetString('proxyType', 'system')
        FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetString('proxyUrl', self.proxyUrlEdit.text())
        self.saveAndClose.emit()

    def retranslateUi(self):
        self.noProxy.setText(QtGui.QApplication.translate("AddonsInstaller", "No proxy", None, QtGui.QApplication.UnicodeUTF8))
        self.systemProxy.setText(QtGui.QApplication.translate("AddonsInstaller", "Use system proxy", None, QtGui.QApplication.UnicodeUTF8))
        self.userProxy.setText(QtGui.QApplication.translate("AddonsInstaller", "User defined proxy:", None, QtGui.QApplication.UnicodeUTF8))
        self.urlEditLabel.setText(QtGui.QApplication.translate("AddonsInstaller", "Proxy URL:", None, QtGui.QApplication.UnicodeUTF8))
        self.buttonApply.setText(QtGui.QApplication.translate("AddonsInstaller", "Save && Close", None, QtGui.QApplication.UnicodeUTF8))


class AddonsInstaller(QtGui.QDialog):

    def __init__(self):
        QtGui.QDialog.__init__(self)
        self.repos = []
        self.macros = []
        self.setObjectName("AddonsInstaller")
        self.resize(326, 304)
        self.verticalLayout = QtGui.QVBoxLayout(self)
        self.tabWidget = QtGui.QTabWidget()
        self.verticalLayout.addWidget(self.tabWidget)
        self.listWorkbenches = QtGui.QListWidget()
        self.listWorkbenches.setIconSize(QtCore.QSize(16,16))
        self.tabWidget.addTab(self.listWorkbenches,"")
        self.listMacros = QtGui.QListWidget()
        self.listMacros.setIconSize(QtCore.QSize(16,16))
        self.tabWidget.addTab(self.listMacros,"")

        self.proxyPanel = ProxyPanel()
        self.tabWidget.addTab(self.proxyPanel, "")

        self.labelDescription = QtGui.QLabel()
        self.labelDescription.setMinimumSize(QtCore.QSize(0, 75))
        self.labelDescription.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.labelDescription.setWordWrap(True)
        self.verticalLayout.addWidget(self.labelDescription)

        self.progressBar = QtGui.QProgressBar(self)
        #self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        #self.progressBar.hide()
        self.progressBar.setRange(0,0)
        self.verticalLayout.addWidget(self.progressBar)

        self.horizontalLayout = QtGui.QHBoxLayout()
        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.buttonInstall = QtGui.QPushButton()
        icon = QtGui.QIcon.fromTheme("download")
        self.buttonInstall.setIcon(icon)
        self.horizontalLayout.addWidget(self.buttonInstall)
        self.buttonRemove = QtGui.QPushButton()
        icon = QtGui.QIcon.fromTheme("edit-delete")
        self.buttonRemove.setIcon(icon)
        self.horizontalLayout.addWidget(self.buttonRemove)
        self.buttonCancel = QtGui.QPushButton()
        icon = QtGui.QIcon.fromTheme("cancel")
        self.buttonCancel.setIcon(icon)
        self.horizontalLayout.addWidget(self.buttonCancel)
        self.verticalLayout.addLayout(self.horizontalLayout)

        self.retranslateUi()

        self.proxyPanel.saveAndClose.connect(self.reject)
        QtCore.QObject.connect(self.buttonCancel, QtCore.SIGNAL("clicked()"), self.reject)
        QtCore.QObject.connect(self.buttonInstall, QtCore.SIGNAL("clicked()"), self.install)
        QtCore.QObject.connect(self.buttonRemove, QtCore.SIGNAL("clicked()"), self.remove)
        QtCore.QObject.connect(self.labelDescription, QtCore.SIGNAL("linkActivated(QString)"), self.showlink)
        QtCore.QObject.connect(self.listWorkbenches, QtCore.SIGNAL("currentRowChanged(int)"), self.show)
        QtCore.QObject.connect(self.tabWidget, QtCore.SIGNAL("currentChanged(int)"), self.switchtab)
        QtCore.QObject.connect(self.listMacros, QtCore.SIGNAL("currentRowChanged(int)"), self.show_macro)
        QtCore.QMetaObject.connectSlotsByName(self)
        self.update()

    def retranslateUi(self):
        self.setWindowTitle(QtGui.QApplication.translate("AddonsInstaller", "Addons installer", None))
        self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Downloading addons list...", None))
        self.buttonCancel.setText(QtGui.QApplication.translate("AddonsInstaller", "Close", None))
        self.buttonInstall.setText(QtGui.QApplication.translate("AddonsInstaller", "Install / update", None))
        self.buttonRemove.setText(QtGui.QApplication.translate("AddonsInstaller", "Remove", None))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.listWorkbenches), QtGui.QApplication.translate("AddonsInstaller", "Workbenches", None))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.listMacros), QtGui.QApplication.translate("AddonsInstaller", "Macros", None))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.proxyPanel), QtGui.QApplication.translate("AddonsInstaller", "Proxy", None))

    def update(self):
        self.listWorkbenches.clear()
        self.repos = []
        self.info_worker = InfoWorker()
        self.info_worker.addon_repos.connect(self.update_repos)
        self.update_worker = UpdateWorker()
        self.update_worker.info_label.connect(self.set_information_label)
        self.update_worker.addon_repo.connect(self.add_addon_repo)
        self.update_worker.progressbar_show.connect(self.show_progress_bar)
        self.update_worker.start()

    def add_addon_repo(self, addon_repo):
        self.repos.append(addon_repo)
        if addon_repo[2] == 1 :
            self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(addon_repo[0]) + str(" (Installed)")))
        else:
            self.listWorkbenches.addItem("        "+str(addon_repo[0]))

    def set_information_label(self, label):
        self.labelDescription.setText(label)

    def show(self,idx):
        if self.repos and idx >= 0:
            self.show_worker = ShowWorker(self.repos, idx)
            self.show_worker.info_label.connect(self.set_information_label)
            self.show_worker.addon_repos.connect(self.update_repos)
            self.show_worker.progressbar_show.connect(self.show_progress_bar)
            self.show_worker.start()

    def show_macro(self,idx):
        if self.macros and idx >= 0:
            self.showmacro_worker = ShowMacroWorker(self.macros, idx)
            self.showmacro_worker.info_label.connect(self.set_information_label)
            self.showmacro_worker.update_macro.connect(self.update_macro)
            self.showmacro_worker.progressbar_show.connect(self.show_progress_bar)
            self.showmacro_worker.start()

    def switchtab(self,idx):
        if idx == 1:
            if not self.macros:
                self.listMacros.clear()
                self.macros = []
                self.macro_worker = MacroWorker()
                self.macro_worker.add_macro.connect(self.add_macro)
                self.macro_worker.info_label.connect(self.set_information_label)
                self.macro_worker.progressbar_show.connect(self.show_progress_bar)
                self.macro_worker.start()

    def update_repos(self, repos):
        self.repos = repos

    def add_macro(self, macro):
        self.macros.append(macro)
        if macro[1] == 1:
            self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)")))
        else:
            self.listMacros.addItem("        "+str(macro[0]))

    def update_macro(self, idx, macro):
        self.macros[idx] = macro

    def showlink(self,link):
        "opens a link with the system browser"
        #print("clicked: ",link)
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(link, QtCore.QUrl.TolerantMode))

    def install(self):
        if self.tabWidget.currentIndex() == 0:
            idx = self.listWorkbenches.currentRow()
            self.install_worker = InstallWorker(self.repos, idx)
            self.install_worker.info_label.connect(self.set_information_label)
            self.install_worker.progressbar_show.connect(self.show_progress_bar)
            self.install_worker.start()
        elif self.tabWidget.currentIndex() == 1:
            macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
            macro = self.macros[self.listMacros.currentRow()]
            if len(macro) < 5:
                self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to install", None))
                return
            macroname = "Macro_"+macro[0]+".FCMacro"
            macroname = macroname.replace(" ","_")
            macrofilename = os.path.join(macropath,macroname)
            macrofile = open(macrofilename,"wb")
            macrofile.write(macro[3])
            macrofile.close()
            self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully installed. The macro is now available from the Macros dialog.", None))
        self.update_status()

    def show_progress_bar(self, state):
        if state == True:
            self.listWorkbenches.setEnabled(False)
            self.listMacros.setEnabled(False)
            self.buttonInstall.setEnabled(False)
            self.buttonRemove.setEnabled(False)
            self.progressBar.show()
        else:
            self.progressBar.hide()
            self.listWorkbenches.setEnabled(True)
            self.listMacros.setEnabled(True)
            self.buttonInstall.setEnabled(True)
            self.buttonRemove.setEnabled(True)

    def remove(self):
        if self.tabWidget.currentIndex() == 0:
            idx = self.listWorkbenches.currentRow()
            basedir = FreeCAD.ConfigGet("UserAppData")
            moddir = basedir + os.sep + "Mod"
            clonedir = basedir + os.sep + "Mod" + os.sep + self.repos[idx][0]
            if os.path.exists(clonedir):
                shutil.rmtree(clonedir)
                self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Addon successfully removed. Please restart FreeCAD", None))
            else:
                self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to remove this addon", None))
        elif self.tabWidget.currentIndex() == 1:
            macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
            macro = self.macros[self.listMacros.currentRow()]
            if macro[1] != 1:
                return
            macroname = "Macro_"+macro[0]+".FCMacro"
            macroname = macroname.replace(" ","_")
            macrofilename = os.path.join(macropath,macroname)
            if os.path.exists(macrofilename):
                os.remove(macrofilename)
                self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully removed.", None))
        self.update_status()

    def update_status(self):
        self.listWorkbenches.clear()
        self.listMacros.clear()
        moddir = FreeCAD.ConfigGet("UserAppData") + os.sep + "Mod"
        macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
        for wb in self.repos:
            if os.path.exists(os.path.join(moddir,wb[0])):
                self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(wb[0]) + str(" (Installed)")))
                wb[2] = 1
            else:
                self.listWorkbenches.addItem("        "+str(wb[0]))
                wb[2] = 0
        for macro in self.macros:
            if os.path.exists(os.path.join(macropath,"Macro_"+macro[0].replace(" ","_")+".FCMacro")):
                self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)")))
                macro[1] = 1
            else:
                self.listMacros.addItem("        "+str(macro[0]))
                macro[1] = 0


class UpdateWorker(QtCore.QThread):

    info_label = QtCore.Signal(str)
    addon_repo = QtCore.Signal(object)
    progressbar_show = QtCore.Signal(bool)

    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        "populates the list of addons"
        self.progressbar_show.emit(True)

        # Configure proxy
        proxyType = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetString('proxyType', 'none')
        if proxyType == 'system':
            FreeCAD.Console.PrintMessage("Using system proxy settings\n")
            proxy = urllib2.ProxyHandler()      # empty argument should use system-wide proxy from OS network settings
        elif proxyType == 'user':
            proxyUrl = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetString('proxyUrl', '')
            FreeCAD.Console.PrintMessage("Using proxy {}\n".format(proxyUrl))
            proxy = urllib2.ProxyHandler({'http' : proxyUrl, 'https' : proxyUrl})
        else:
            proxy = urllib2.ProxyHandler({})    # Default: No proxy

        proxy_opener = urllib2.build_opener(proxy)
        urllib2.install_opener(proxy_opener)

        u = openUrl("https://github.com/FreeCAD/FreeCAD-addons")
        p = u.read()
        u.close()
        p = p.replace("\n"," ")
        p = re.findall("octicon-file-submodule(.*?)message",p)
        basedir = FreeCAD.ConfigGet("UserAppData")
        moddir = basedir + os.sep + "Mod"
        repos = []
        for l in p:
            #name = re.findall("data-skip-pjax=\"true\">(.*?)<",l)[0]
            name = re.findall("title=\"(.*?) @",l)[0]
            self.info_label.emit(name)
            #url = re.findall("title=\"(.*?) @",l)[0]
            url = "https://github.com/" + re.findall("href=\"\/(.*?)\/tree",l)[0]
            addondir = moddir + os.sep + name
            if not os.path.exists(addondir):
                state = 0
            else:
                state = 1
            repos.append([name,url,state])
            self.addon_repo.emit([name,url,state])
        if not repos:
            self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to download addons list.", None))
        else:
            self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Workbenches list was updated.", None))
        self.progressbar_show.emit(False)
        self.stop = True


class InfoWorker(QtCore.QThread):
    addon_repos = QtCore.Signal(object)

    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        i = 0
        for repo in self.repos:
            url = repo[1]
            u = openUrl(url)
            p = u.read()
            u.close()
            desc = re.findall("<meta content=\"(.*?)\" name", p)[3]
            self.repos[i].append(desc)
            i += 1
            self.addon_repos.emit(self.repos)
        self.stop = True


class MacroWorker(QtCore.QThread):

    add_macro = QtCore.Signal(object)
    info_label = QtCore.Signal(str)
    progressbar_show = QtCore.Signal(bool)

    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        "populates the list of addons"
        self.info_label.emit("Downloading list of macros...")
        self.progressbar_show.emit(True)
        macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro"))
        u = openUrl("http://www.freecadweb.org/wiki/index.php?title=Macros_recipes")
        p = u.read()
        u.close()
        macros = re.findall("title=\"(Macro.*?)\"",p)
        macros = [mac for mac in macros if (not("translated" in mac))]
        macros.sort()
        for mac in macros:
            macname = mac[6:]
            macname = macname.replace("&amp;","&")
            if not (macname in MACROS_BLACKLIST):
                macfile = mac.replace(" ","_")+".FCMacro"
                if os.path.exists(os.path.join(macropath,macfile)):
                    installed = 1
                else:
                    installed = 0
                self.add_macro.emit([macname,installed])
        self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "List of macros successfully retrieved.", None))
        self.progressbar_show.emit(False)
        self.stop = True


class ShowWorker(QtCore.QThread):

    info_label = QtCore.Signal(str)
    addon_repos = QtCore.Signal(object)
    progressbar_show = QtCore.Signal(bool)

    def __init__(self, repos, idx):
        QtCore.QThread.__init__(self)
        self.repos = repos
        self.idx = idx

    def run(self):
        self.progressbar_show.emit(True)
        self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving description...", None))
        if len(self.repos[self.idx]) == 4:
            desc = self.repos[self.idx][3]
        else:
            url = self.repos[self.idx][1]
            self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving info from ", None) + str(url))
            u = openUrl(url)
            p = u.read()
            u.close()
            desc = re.findall("<meta content=\"(.*?)\" name",p)[4]
            self.repos[self.idx].append(desc)
            self.addon_repos.emit(self.repos)
        if self.repos[self.idx][2] == 1 :
            message = "<strong>" + QtGui.QApplication.translate("AddonsInstaller", "<strong>This addon is already installed.", None) + "</strong><br>" + desc + ' - <a href="' + self.repos[self.idx][1] + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + self.repos[self.idx][1] + '</span></a>'
        else:
            message = desc + ' - <a href="' + self.repos[self.idx][1] + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + self.repos[self.idx][1] + '</span></a>'
        self.info_label.emit( message )
        self.progressbar_show.emit(False)
        self.stop = True


class ShowMacroWorker(QtCore.QThread):

    info_label = QtCore.Signal(str)
    update_macro = QtCore.Signal(int,object)
    progressbar_show = QtCore.Signal(bool)

    def __init__(self, macros, idx):
        QtCore.QThread.__init__(self)
        self.macros = macros
        self.idx = idx

    def run(self):
        self.progressbar_show.emit(True)
        self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving description...", None))
        if len(self.macros[self.idx]) > 2:
            desc = self.macros[self.idx][2]
            url = self.macros[self.idx][4]
        else:
            mac = self.macros[self.idx][0].replace(" ","_")
            mac = mac.replace("&","%26")
            mac = mac.replace("+","%2B")
            url = "http://www.freecadweb.org/wiki/index.php?title=Macro_"+mac
            self.info_label.emit("Retrieving info from " + str(url))
            u = openUrl(url)
            p = u.read()
            u.close()
            code = re.findall("<pre>(.*?)<\/pre>",p.replace("\n","--endl--"))
            if code:
                code = code[0]
                code = code.replace("--endl--","\n")
            else:
                self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to fetch the code of this macro.", None))
                self.progressbar_show.emit(False)
                self.stop = True
                return
            desc = re.findall("<td class=\"ctEven left macro-description\">(.*?)<\/td>",p.replace("\n"," "))
            if desc:
                desc = desc[0]
            else:
                self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to retrieve a description for this macro.", None))
                desc = "No description available"
            # clean HTML escape codes
            try:
                from HTMLParser import HTMLParser
            except ImportError:
                from html.parser import HTMLParser
            try:
                code = code.decode("utf8")
                code = HTMLParser().unescape(code)
                code = code.encode("utf8")
                code = code.replace("\xc2\xa0", " ")
            except:
                FreeCAD.Console.PrintWarning("Unable to clean macro code: "+mac+"\n")
            self.update_macro.emit(self.idx,self.macros[self.idx]+[desc,code,url])
        if self.macros[self.idx][1] == 1 :
            message = "<strong>" + QtGui.QApplication.translate("AddonsInstaller", "<strong>This addon is already installed.", None) + "</strong><br>" + desc + ' - <a href="' + url + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + url + '</span></a>'
        else:
            message = desc + ' - <a href="' + url + '"><span style="word-wrap: break-word;width:15em;text-decoration: underline; color:#0000ff;">' + url + '</span></a>'
        self.info_label.emit( message )
        self.progressbar_show.emit(False)
        self.stop = True


class InstallWorker(QtCore.QThread):

    info_label = QtCore.Signal(str)
    progressbar_show = QtCore.Signal(bool)

    def __init__(self, repos, idx):
        QtCore.QThread.__init__(self)
        self.idx = idx
        self.repos = repos

    def run(self):
        "installs or updates the selected addon"
        git = None
        try:
            import git
        except:
            self.info_label.emit("python-git not found.")
            FreeCAD.Console.PrintWarning("python-git not found. Using standard download instead.\n")
            try:
                import zipfile,StringIO
            except:
                self.info_label.emit("no zip support.")
                FreeCAD.Console.PrintError("your version of python doesn't appear to support ZIP files. Unable to proceed.\n")
                return
        if self.idx < 0:
            return
        if not self.repos:
            return
        if NOGIT:
            git = None
        basedir = FreeCAD.ConfigGet("UserAppData")
        moddir = basedir + os.sep + "Mod"
        if not os.path.exists(moddir):
            os.makedirs(moddir)
        clonedir = moddir + os.sep + self.repos[self.idx][0]
        self.progressbar_show.emit(True)
        if os.path.exists(clonedir):
            self.info_label.emit("Updating module...")
            if git:
                repo = git.Git(clonedir)
                answer = repo.pull()
            else:
                answer = self.download(self.repos[self.idx][1],clonedir)
        else:
            if git:
                self.info_label.emit("Cloning module...")
                repo = git.Repo.clone_from(self.repos[self.idx][1], clonedir, branch='master')
            else:
                self.info_label.emit("Downloading module...")
                self.download(self.repos[self.idx][1],clonedir)
            answer = QtGui.QApplication.translate("AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.", None)
            # symlink any macro contained in the module to the macros folder
            macrodir = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath")
            for f in os.listdir(clonedir):
                if f.lower().endswith(".fcmacro"):
                    symlink(clonedir+os.sep+f,macrodir+os.sep+f)
                    FreeCAD.ParamGet('User parameter:Plugins/'+self.repos[self.idx][0]).SetString("destination",clonedir)
                    answer += QtGui.QApplication.translate("AddonsInstaller", "A macro has been installed and is available the Macros menu", None) + ": <b>"
                    answer += f + "</b>"
        self.info_label.emit(answer)
        self.progressbar_show.emit(False)
        self.stop = True

    def download(self,giturl,clonedir):
        "downloads and unzip from github"
        import StringIO,zipfile
        bakdir = None
        if os.path.exists(clonedir):
            bakdir = clonedir+".bak"
            if os.path.exists(bakdir):
                shutil.rmtree(bakdir)
            os.rename(clonedir,bakdir)
        os.makedirs(clonedir)
        zipurl = giturl+"/archive/master.zip"
        try:
            print("Downloading "+zipurl)
            u = urllib2.urlopen(zipurl)
        except:
            return QtGui.QApplication.translate("AddonsInstaller", "Error: Unable to download", None) + " " + zipurl
        zfile = StringIO.StringIO()
        zfile.write(u.read())
        zfile = zipfile.ZipFile(zfile)
        master = zfile.namelist()[0] # github will put everything in a subfolder
        zfile.extractall(clonedir)
        u.close()
        zfile.close()
        for filename in os.listdir(clonedir+os.sep+master):
            shutil.move(clonedir+os.sep+master+os.sep+filename, clonedir+os.sep+filename)
        os.rmdir(clonedir+os.sep+master)
        if bakdir:
            shutil.rmtree(bakdir)
        return QtGui.QApplication.translate("AddonsInstaller", "Successfully installed", None) + " " + zipurl


# first use dialog
readWarning = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetBool('readWarning',False)
if not readWarning:
    if QtGui.QMessageBox.warning(None,"FreeCAD",QtGui.QApplication.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!", None), QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok) != QtGui.QMessageBox.StandardButton.Cancel:
        FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetBool('readWarning',True)
        readWarning = True

if readWarning:
    dialog = AddonsInstaller()
    dialog.exec_()