Beispiel #1
0
class GenericDbManager(QObject):
    """
    This class manages the permissions on dsgtools databases.
    """
    def __init__(self,
                 serverAbstractDb,
                 dbDict,
                 edgvVersion,
                 parentWidget=None):
        super(GenericDbManager, self).__init__()
        self.parentWidget = parentWidget
        self.dbDict = dbDict
        self.serverAbstractDb = serverAbstractDb
        self.adminDb = self.instantiateAdminDb(serverAbstractDb)
        self.utils = Utils()
        self.extensionDict = {
            'EarthCoverage': '.dsgearthcov',
            'Customization': '.dsgcustom',
            'Style': '.dsgstyle',
            'ValidationConfig': '.dsgvalidcfg',
            'FieldToolBoxConfig': '.reclas',
            'Permission': '.dsgperm'
        }
        self.edgvVersion = edgvVersion

    def getManagerType(self):
        return str(self.__class__).split('.')[-1].replace('\'>', '').replace(
            'Manager', '')

    def instantiateAbstractDb(self, dbName):
        """
        Instantiates an abstractDb.
        """
        if dbName not in self.dbDict.keys():
            (host, port, user,
             password) = self.serverAbstractDb.getParamsFromConectedDb()
            abstractDb = DbFactory().createDbFactory('QPSQL')
            abstractDb.connectDatabaseWithParameters(host, port, dbName, user,
                                                     password)
        else:
            abstractDb = self.dbDict[dbName]
        return abstractDb

    def instantiateAdminDb(self, serverAbstractDb):
        """
        Instantiates dsgtools_admindb in the same server as serverAbstractDb. 
        If dsgtools_admindb does not exists, instantiateAdminDb calls createAdminDb
        """
        (host, port, user,
         password) = serverAbstractDb.getParamsFromConectedDb()
        adminDb = DbFactory().createDbFactory('QPSQL')
        if not serverAbstractDb.hasAdminDb():
            return self.createAdminDb(serverAbstractDb, adminDb, host, port,
                                      user, password)
        adminDb.connectDatabaseWithParameters(host, port, 'dsgtools_admindb',
                                              user, password)
        return adminDb

    def instantiateTemplateDb(self, edgvVersion):
        """
        Instantiates a templateDb in the same server as serverAbstractDb. 
        If template does not exists, instantiateAdminDb calls createTemplate
        """
        templateName = self.serverAbstractDb.getTemplateName(edgvVersion)
        hasTemplate = self.serverAbstractDb.checkTemplate(edgvVersion)
        if not hasTemplate:
            self.serverAbstractDb.createTemplateDatabase(edgvVersion)
            templateDb = self.instantiateAbstractDb(templateName)
            templateDb.setStructureFromSql(edgvVersion, 4674)
            templateDb.setDbAsTemplate(version=edgvVersion)
        else:
            templateDb = self.instantiateAbstractDb(templateName)
        return templateDb

    def createAdminDb(self, serverAbstractDb, adminDb, host, port, user,
                      password):
        """
        Creates dsgtools_admindb
        """
        serverAbstractDb.createAdminDb()
        adminDb.connectDatabaseWithParameters(host, port, 'dsgtools_admindb',
                                              user, password)
        sqlPath = adminDb.getCreationSqlPath('admin')
        adminDb.runSqlFromFile(sqlPath)
        return adminDb

    def getSettings(self):
        """
        Gets all profiles from public.permission_profile
        """
        settingType = self.getManagerType()
        return self.adminDb.getAllSettingsFromAdminDb(settingType)

    def getSetting(self, name, edgvVersion):
        """
        Get setting from corresponding table on dsgtools_admindb
        """
        settingType = self.getManagerType()
        settingDict = json.loads(
            self.adminDb.getSettingFromAdminDb(settingType, name, edgvVersion))
        if not settingDict:
            raise Exception(
                self.tr("Setting ") + settingType +
                self.tr(" not found on dsgtools_admindb!"))
        return settingDict

    def createSetting(self, settingName, edgvVersion, jsonDict):
        """
        Creates setting on dsgtools_admindb.
        """
        settingType = self.getManagerType()
        if isinstance(jsonDict, dict):
            jsonDict = json.dumps(jsonDict, sort_keys=True, indent=4)
        self.adminDb.insertSettingIntoAdminDb(settingType, settingName,
                                              jsonDict, edgvVersion)

    def updateSetting(self, settingName, newJsonDict, edgvVersion=None):
        """
        Generic update. Can be reimplenented in child methods.
        1. Get property dict from adminDb 
        """
        if not edgvVersion:
            edgvVersion = self.edgvVersion
        errorDict = dict()
        successList = []
        settingType = self.getManagerType()
        propertyDict = self.adminDb.getPropertyPerspectiveDict(
            settingType, DsgEnums.Property, versionFilter=edgvVersion)
        if settingName in propertyDict.keys():
            rollbackList = []
            self.adminDb.db.transaction()
            try:
                for dbName in propertyDict[settingName]:
                    abstractDb = self.instantiateAbstractDb(dbName)
                    abstractDb.db.transaction()
                    rollbackList.append(abstractDb)
                    self.updateMaterializationFromDatabase(
                        abstractDb, propertyDict)
                    abstractDb.updateRecordFromPropertyTable(
                        settingType, settingName, edgvVersion, newJsonDict)
                self.adminDb.updateRecordFromPropertyTable(
                    settingType, settingName, edgvVersion, newJsonDict)
                for abstractDb in rollbackList:
                    abstractDb.db.commit()
                self.adminDb.db.commit()
                successList = [i for i in propertyDict[settingName]]
            except Exception as e:
                for abstractDb in rollbackList:
                    abstractDb.db.rollback()
                self.adminDb.db.rollback()
                errorDict[dbName] = ':'.join(e.args)
        return (successList, errorDict)

    def importSetting(self, fullFilePath):
        """
        Function to import profile into dsgtools_admindb. It has the following steps:
        1. Reads inputJsonFilePath and parses it into a python dict;
        2. Validates inputPermissionDict;
        3. Tries to insert into database, if there is an error, abstractDb raises an error which is also raised by importProfile
        """
        #getting profile name
        settingName = os.path.basename(fullFilePath).split('.')[0]
        #getting json
        inputJsonDict, inputJson = self.utils.readJsonFile(
            fullFilePath, returnFileAndDict=True)
        #error handling and json validation
        if inputJsonDict == dict():
            raise Exception(self.tr("Not valid DsgTools property file!"))
        if not self.validateJsonSetting(inputJsonDict):
            raise Exception(self.tr("Not valid DsgTools property file!"))
        if 'version' in inputJsonDict.keys():
            edgvVersion = inputJsonDict['version']
        else:
            edgvVersion = inputJsonDict.keys()[0].split('_')[-1]
        try:
            self.createSetting(settingName, edgvVersion, inputJson)
        except Exception as e:
            raise Exception(
                self.tr("Error importing setting ") + settingName + ': ' +
                ':'.join(e.args))

    def batchImportSettings(self, profilesDir):
        """
        1. Get all properties in profilesDir;
        2. Import each using importSetting;
        """
        importList = []
        for profile in os.walk(profilesDir).next()[2]:
            if self.extensionDict[self.getManagerType()] in os.path.basename(
                    profile):
                importList.append(os.path.join(profilesDir, profile))
        for profileFile in importList:
            self.importSetting(profileFile)

    def exportSetting(self, profileName, edgvVersion, outputPath):
        """
        1. Get setting from dsgtools_admindb;
        2. Export it to outputPath.
        """
        jsonDict = self.getSetting(profileName, edgvVersion)
        outputFile = os.path.join(
            outputPath,
            profileName + self.extensionDict[self.getManagerType()])
        with open(outputFile, 'w') as outfile:
            json.dump(jsonDict, outfile, sort_keys=True, indent=4)

    def batchExportSettings(self, outputDir):
        """
        1. Get all settings from corresponding table in dsgtools_admindb;
        2. Export each using exportSetting.
        """
        settingDict = self.getSettings()
        for edgvVersion in settingDict.keys():
            outputPath = os.path.join(outputDir, edgvVersion)
            if not os.path.exists(outputPath):
                os.makedirs(outputPath)
            for profileName in settingDict[edgvVersion]:
                self.exportSetting(profileName, edgvVersion, outputPath)

    def getPropertyPerspectiveDict(self,
                                   viewType=DsgEnums.Property,
                                   versionFilter=None):
        """
        Gets a dict in the format:
        if viewType == 'customization': {customizationName: ['-list of databases with customization']}
        if viewType == 'database': {databaseName: ['-list of customizations with customization']}
        """
        settingType = self.getManagerType()
        return self.adminDb.getPropertyPerspectiveDict(
            settingType, viewType, versionFilter=versionFilter)

    def getSettingVersion(self, settingName):
        settingType = self.getManagerType()
        return self.adminDb.getSettingVersion(settingType, settingName)

    def validateJsonSetting(self, inputJsonDict):
        """
        reimplemented in each child
        """
        return True

    def getRecordFromAdminDb(self, propertyName, edgvVersion):
        settingType = self.getManagerType()
        return self.adminDb.getRecordFromAdminDb(settingType, propertyName,
                                                 edgvVersion)

    def createAndInstall(self,
                         configName,
                         newJsonDict,
                         edgvVersion,
                         dbList=[]):
        self.createSetting(configName, edgvVersion, newJsonDict)
        return self.installSetting(configName, dbNameList=dbList)

    def installSetting(self, configName, dbNameList=[]):
        """
        Generic install. Can be reimplenented in child methods.
        """
        errorDict = dict()
        settingType = self.getManagerType()
        if dbNameList == []:
            dbNameList = self.dbDict.keys()
        successList = []
        configEdgvVersion = self.getSettingVersion(configName)
        for dbName in dbNameList:
            abstractDb = self.instantiateAbstractDb(dbName)
            edgvVersion = abstractDb.getDatabaseVersion()
            if edgvVersion != configEdgvVersion:
                errorDict[dbName] = self.tr('Database version missmatch.')
                continue
            recDict = self.adminDb.getRecordFromAdminDb(
                settingType, configName, edgvVersion)
            try:
                if not abstractDb.checkIfExistsConfigTable(settingType):
                    abstractDb.createPropertyTable(settingType,
                                                   useTransaction=True)
            except Exception as e:
                errorDict[dbName] = ':'.join(e.args)
                continue
            try:
                abstractDb.db.transaction()
                self.adminDb.db.transaction()
                self.materializeIntoDatabase(
                    abstractDb, recDict
                )  #step done when property management involves changing database structure
                abstractDb.insertRecordInsidePropertyTable(
                    settingType, recDict, edgvVersion)
                dbOid = abstractDb.getDbOID()
                self.adminDb.insertInstalledRecordIntoAdminDb(
                    settingType, recDict, dbOid)
                abstractDb.db.commit()
                self.adminDb.db.commit()
            except Exception as e:
                abstractDb.db.rollback()
                self.adminDb.db.rollback()
                errorDict[dbName] = ':'.join(e.args)
            successList.append(dbName)
        return (successList, errorDict)

    def deleteSetting(self, configName, dbNameList=[]):
        """
        Generic remove. Can be reimplenented in child methods.
        1. Get property dict from adminDb 
        """
        errorDict = dict()
        successList = []
        settingType = self.getManagerType()
        propertyDict = self.adminDb.getPropertyPerspectiveDict(
            settingType, DsgEnums.Property)
        if configName in propertyDict.keys():
            for dbName in propertyDict[configName]:
                abstractDb = self.instantiateAbstractDb(dbName)
                edgvVersion = abstractDb.getDatabaseVersion()
                try:
                    abstractDb.db.transaction()
                    self.adminDb.db.transaction()
                    self.undoMaterializationFromDatabase(
                        abstractDb, configName, settingType, edgvVersion
                    )  #step done when property management involves changing database structure
                    abstractDb.removeRecordFromPropertyTable(
                        settingType, configName, edgvVersion)
                    self.adminDb.removeRecordFromPropertyTable(
                        settingType, configName, edgvVersion)
                    abstractDb.db.commit()
                    self.adminDb.db.commit()
                except Exception as e:
                    abstractDb.db.rollback()
                    self.adminDb.db.rollback()
                    errorDict[dbName] = ':'.join(e.args)
                successList.append(dbName)
        return (successList, errorDict)

    def uninstallSetting(self, configName, dbNameList=[]):
        """
        Generic uninstall. Can be reimplenented in child methods.
        This can uninstall setting on a list of databases or in all databases (if dbNameList == [])
        """
        errorDict = dict()
        successList = []
        settingType = self.getManagerType()
        propertyDict = self.adminDb.getPropertyPerspectiveDict(
            settingType, DsgEnums.Property)
        if configName in propertyDict.keys():
            if dbNameList == []:  #builds filter dbList to uninstall in all installed databases
                dbList = propertyDict[configName]
            else:  #builds filter dbList to uninstall in databases in dbNameList
                dbList = [
                    i for i in propertyDict[configName] if i in dbNameList
                ]
            for dbName in dbList:
                abstractDb = self.instantiateAbstractDb(dbName)
                edgvVersion = abstractDb.getDatabaseVersion()
                try:
                    abstractDb.db.transaction()
                    self.adminDb.db.transaction()
                    self.undoMaterializationFromDatabase(
                        abstractDb, configName, settingType, edgvVersion
                    )  #step done when property management involves changing database structure
                    abstractDb.removeRecordFromPropertyTable(
                        settingType, configName, edgvVersion)
                    self.adminDb.uninstallPropertyOnAdminDb(settingType,
                                                            configName,
                                                            edgvVersion,
                                                            dbName=dbName)
                    abstractDb.db.commit()
                    self.adminDb.db.commit()
                except Exception as e:
                    abstractDb.db.rollback()
                    self.adminDb.db.rollback()
                    errorDict[dbName] = ':'.join(e.args)
                successList.append(dbName)
        return (successList, errorDict)

    def materializeIntoDatabase(self, abstractDb, propertyDict):
        """
        Method that is reimplemented in each child when installing a property involves changing any sort of database structure
        """
        pass

    def undoMaterializationFromDatabase(self, abstractDb, configName,
                                        settingType, edgvVersion):
        """
        Method that is reimplemented in each child when uninstalling a property involves changing any sort of database structure
        """
        pass

    def hasStructuralChanges(self, dbNameList):
        """
        Method that is reimplemented in each child
        """
        return []
class CreateDatabaseCustomization(QtGui.QDialog, FORM_CLASS):
    def __init__(self, customizationName, abstractDb, edgvVersion, customizationManager, customJsonDict = None, parent = None):
        """Constructor."""
        super(self.__class__, self).__init__(parent)
        self.setupUi(self)
        self.customizationManager = customizationManager
        self.abstractDb = abstractDb
        self.edgvVersion = edgvVersion
        self.customizationName = customizationName
        self.contentsDict = dict()
        self.populateCustomizationCombo()
        self.setWidgetsEnabled(True)
        self.utils = Utils()
        if customJsonDict:
            self.createWidgetsFromCustomJsonDict(customJsonDict)

    def clearWidgets(self):
        rootItem = self.customizationTreeWidget.invisibleRootItem()
        childNodeCount = rootItem.childCount()
        #remove widgets
        for i in range(childNodeCount):
            typeChild = rootItem.child(i)
            childCount = typeChild.childCount()
            childTextList = []
            for j in range(childCount):
                childTextList.append(typeChild.child(i).text(0))
            for childText in childTextList:
                self.removeWidget(widgetText = childText)
    
    def setWidgetsEnabled(self, enabled):
        self.customizationSelectionComboBox.setEnabled(enabled)
        self.addAttributePushButton.setEnabled(enabled)
        self.customizationTreeWidget.setEnabled(enabled)
        self.removeSelectedPushButton.setEnabled(enabled)
    
    def populateCustomizationCombo(self):
        '''
        Populates the customization combo and also defines customDict.
        '''
        self.customDict = dict()
        self.customDict['attribute'] = self.tr('Attribute Customization')
        self.customDict['class'] = self.tr('Class Customization')
        self.customDict['codeName'] = self.tr('Code Name Customization')
        self.customDict['default'] = self.tr('Default Customization')
        self.customDict['domain'] = self.tr('Domain Customization')
        self.customDict['domainValue'] = self.tr('Domain Value Customization')
        self.customDict['nullity'] = self.tr('Attribute Nullity Customization')
        self.customDict['filter'] = self.tr('Attribute Filter Customization')
        rootNode = self.customizationTreeWidget.invisibleRootItem()
        for type in self.customDict.keys():
            if self.customDict[type] not in self.contentsDict.keys():
                self.contentsDict[self.customDict[type]] = dict()
            self.customizationSelectionComboBox.addItem(self.customDict[type])
            self.contentsDict[self.customDict[type]]['widgetList'] = []
            self.contentsDict[self.customDict[type]]['treeItem'] = self.createItem(rootNode, self.customDict[type], 0)
        self.customizationTreeWidget.expandAll()
    
    @pyqtSlot(bool)
    def on_addAttributePushButton_clicked(self):
        if self.customizationSelectionComboBox.currentText() == self.tr('Attribute Customization'):
            self.addAttributeWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Class Customization'):
            self.addClassWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Code Name Customization'):
            self.addCodeNameWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Default Customization'):
            self.addDefaultWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Domain Customization'):
            self.addDomainWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Domain Value Customization'):
            self.addDomainValueWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Attribute Nullity Customization'):
            self.addNullityWidget()
        elif self.customizationSelectionComboBox.currentText() == self.tr('Attribute Filter Customization'):
            self.addFilterWidget()
        else:
            QMessageBox.warning(self, self.tr('Warning'), self.tr('Select a custom operation!'))
    
    def addWidgetItem(self, contentsKey, widgetTitle, widget):
        widgetList = self.contentsDict[contentsKey]['widgetList']
        if len(widgetList) > 0:
            i = int(widgetList[-1].layout().itemAt(0).widget().getTitle().split('#')[-1])
        else:
            i = 0
        title = widgetTitle+' #{0}'.format(i+1) #add number
        widget.setTitle(title)
        self.contentsDict[contentsKey]['widgetList'].append(self.addWidget(widget, title))
        self.createItem(self.contentsDict[contentsKey]['treeItem'], title, 0)
    
    def addAttributeWidget(self,uiParameterJsonDict=None):
        widget = NewAttributeWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Attribute Customization'), self.tr('New Custom Attribute'), widget)
    
    def addClassWidget(self,uiParameterJsonDict=None):
        widget = NewClassWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Class Customization'), self.tr('New Custom Class'), widget)
    
    def addCodeNameWidget(self,uiParameterJsonDict=None):
        widget = CodeNameCustomizationWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Code Name Customization'), self.tr('New Custom Code Name'), widget)

    def addDefaultWidget(self,uiParameterJsonDict=None):
        widget = AlterDefaultWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Default Customization'), self.tr('New Custom Default'), widget) 

    def addDomainWidget(self,uiParameterJsonDict=None):
        widget = NewDomainWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Domain Customization'), self.tr('New Custom Domain'), widget)

    def addDomainValueWidget(self,uiParameterJsonDict=None):
        widget = NewDomainValueWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Domain Value Customization'), self.tr('New Domain Value'), widget)

    def addNullityWidget(self,uiParameterJsonDict=None):
        widget = ChangeNullityWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Attribute Nullity Customization'), self.tr('New Custom Attribute Nullity'), widget)

    def addFilterWidget(self,uiParameterJsonDict=None):
        widget = ChangeFilterWidget(self.abstractDb,uiParameterJsonDict = uiParameterJsonDict)
        self.addWidgetItem(self.tr('Attribute Filter Customization'), self.tr('New Custom Attribute Filter'), widget)
    
    def addWidget(self, widget, title):
        layout = QtGui.QFormLayout()
        layout.addRow(widget)
        groupBox = QgsCollapsibleGroupBox(title)
        groupBox.setCollapsed(False)
        groupBox.setSaveCollapsedState(False)
        groupBox.setLayout(layout)
        self.scrollAreaLayout.addWidget(groupBox)
        return groupBox
    
    def createItem(self, parent, text, column):
        item = QtGui.QTreeWidgetItem(parent)
        item.setText(column, text)
        return item
    
    def getWidgetIndexFromTreeItem(self, treeItem):
        parent = treeItem.parent()
        widgetName = treeItem.text(0) 
        if not parent:
            return
        if parent == self.customizationTreeWidget.invisibleRootItem():
            return None
        childCount = parent.childCount()
        for i in range(childCount):
            child = parent.child(i)
            if child.text(0) == widgetName:
                return i
    
    @pyqtSlot(bool, name='on_removeSelectedPushButton_clicked')
    def removeWidget(self, widgetText = None):
        if not widgetText:
            treeItemList = [self.customizationTreeWidget.currentItem()]
        else:
            treeItemList = self.customizationTreeWidget.findItems(widgetText, flags = Qt.MatchExactly)
        if len(treeItemList)>0:
            for treeItem in treeItemList:
                parent = treeItem.parent()
                if parent == self.customizationTreeWidget.invisibleRootItem():
                    return
                idx = self.getWidgetIndexFromTreeItem(treeItem)
                itemToRemove = self.contentsDict[parent.text(0)]['widgetList'].pop(idx)
                itemToRemove.setParent(None)
                self.contentsDict[parent.text(0)]['treeItem'].removeChild(treeItem)
    
    @pyqtSlot()
    def on_buttonBox_accepted(self):
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        exceptionList = []
        customJsonDict = dict()
        for i in self.customDict.keys():
            customJsonDict[i] = []
        correspondenceDict = {self.customDict[i]:i for i in self.customDict.keys()}
        nCustom = 0
        for key in self.contentsDict.keys():
            for widgetItem in self.contentsDict[key]['widgetList']:
                nCustom += 1
        progress = ProgressWidget(1,nCustom,self.tr('Preparing to export customizations... '), parent = self)
        progress.initBar()
        for key in self.contentsDict.keys():
            jsonTagList = []
            for widget in self.contentsDict[key]['widgetList']:
                currJsonItem = {'jsonUi':None, 'dbJsonTagList':[]}
                currentWidget = widget.layout().itemAt(0).widget()
                try:
                    jsonTagList = currentWidget.getJSONTag()
                    jsonUi = currentWidget.getUiParameterJsonDict()
                except Exception as e:
                    exceptionList.append(':'.join(e.args))
                if len(exceptionList) == 0:
                    currJsonItem['jsonUi'] = jsonUi
                    for jsonItem in jsonTagList:
                        if jsonItem not in currJsonItem['dbJsonTagList']:
                            currJsonItem['dbJsonTagList'].append(jsonItem)
                    if currJsonItem not in customJsonDict[correspondenceDict[key]]:
                        customJsonDict[correspondenceDict[key]].append(currJsonItem)
                progress.step()
        QApplication.restoreOverrideCursor()
        if self.validateJsonDict(customJsonDict) and len(exceptionList) == 0:
            versionText = 'database_'+self.edgvVersion
            finalJsonDict = {versionText:customJsonDict}
            self.customizationManager.createSetting(self.customizationName, self.edgvVersion, finalJsonDict)
            QMessageBox.information(self, self.tr('Success!'), self.tr('Database Customization ') + self.customizationName + self.tr(' created successfuly!'))
            #EMIT to reload?
            self.close()
        else:
            msg = ''
            if len(exceptionList)> 0:
                msg += self.tr('\Errors occured while trying to export customs built. Check qgis log for further details.')
                for error in exceptionList:
                    QgsMessageLog.logMessage(self.tr('Customization error: ') + error, "DSG Tools Plugin", QgsMessageLog.CRITICAL)
                QMessageBox.warning(self, self.tr('Error!'), msg)
    
    def validateJsonDict(self, customJsonDict):
        """
        Method to apply validation to customJsonDict
        """
        #TODO
        return True

    def populateWidgetsFromSelectedFile(self):
        jsonFileName = self.selectFileWidget.fileNameList
        customJsonDict = self.utils.readJsonFile(jsonFileName)
        self.createWidgetsFromCustomJsonDict(customJsonDict)
    
    def createWidgetsFromCustomJsonDict(self, customJsonDict):
        for key in customJsonDict.keys():
            for jsonTag in customJsonDict[key]:
                self.createWidgetFromKey(key, jsonTag['jsonUi'])
    
    def createWidgetFromKey(self, key, uiParameterJsonDict):
        if key == 'attribute':
            self.addAttributeWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'class':
            self.addClassWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'codeName':
            self.addCodeNameWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'default':
            self.addDefaultWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'domain':
            self.addDomainWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'domainValue':
            self.addDomainValueWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'nullity':
            self.addNullityWidget(uiParameterJsonDict=uiParameterJsonDict)
        elif key == 'filter':
            self.addFilterWidget(uiParameterJsonDict=uiParameterJsonDict)
        else:
            pass
class SetupEarthCoverage(QtGui.QWizard, FORM_CLASS):
    coverageChanged = pyqtSignal()
    def __init__(self, edgvVersion, areas, lines, oldCoverage, propertyList, enableSetupFromFile = True, onlySetup = False, propertyName = None, parent=None):
        """
        Constructor
        """
        super(self.__class__, self).__init__()
        self.setupUi(self)
        self.utils = Utils()
        self.areas = areas
        self.lines = lines
        self.propertyName = propertyName
        self.edgvVersion = edgvVersion
        self.areasCustomSelector.setTitle(self.tr('Areas'))
        self.linesCustomSelector.setTitle(self.tr('Lines'))
        self.propertyList = propertyList
        self.button(QtGui.QWizard.NextButton).clicked.connect(self.buildTree)
        self.button(QtGui.QWizard.FinishButton).clicked.connect(self.buildDict)
        self.setupWizard(oldCoverage, enableSetupFromFile)
        self.configDict = dict()

    def setupFromFile(self):
        """
        Opens a earth coverage file
        """
        if QMessageBox.question(self, self.tr('Question'), self.tr('Do you want to open an earth coverage file?'), QMessageBox.Ok|QMessageBox.Cancel) == QMessageBox.Cancel:
            return
        filename = QFileDialog.getOpenFileName(self, self.tr('Open Earth Coverage Setup configuration'), '', self.tr('Earth Coverage Files (*.json)'))
        return filename

    def setupWizard(self, oldCoverage, enableSetupFromFile):
        """
        Prepares the wizard
        oldCoverage: old configuration
        """
        if oldCoverage:
            # self.abstractDb.dropCentroids(oldCoverage.keys())
            self.setupUiFromDict(oldCoverage)
            return
        else:
            self.populateFrameListWidget(self.areas)
        if enableSetupFromFile:
            filename = self.setupFromFile()
        else:
            filename = None
        if filename:
            self.setupUiFromFile(filename)
        else:
            self.areasCustomSelector.setFromList(self.areas)
            self.linesCustomSelector.setFromList(self.lines)
        if self.propertyName:
            self.nameLineEdit.setText(self.propertyName)
            self.nameLineEdit.setEnabled(False)
    
    def setupUiFromFile(self, filename):
        """
        Populates ui from parameters of json
        """
        #read json
        jsonDict = self.utils.readJsonFile(filename)
        self.setupUiFromDict(jsonDict)

    def setupUiFromDict(self, jsonDict):
        """
        Populates ui from parameters of json
        """
        #set nameLineEdit
        self.nameLineEdit.setText(jsonDict['configName'])
        #populate listWidget
        self.populateFrameListWidget(self.areas, frame = jsonDict['frameLayer'])
        linesFromList, linesToList, areasFromList, areasToList = self.populateLists(jsonDict['earthCoverageDict'])
        self.areasCustomSelector.setToList(areasToList)
        self.areasCustomSelector.setFromList(areasFromList)
        self.linesCustomSelector.setToList(linesToList)
        self.linesCustomSelector.setFromList(linesFromList)
        self.buildTree()
        self.checkDelimiters(jsonDict['earthCoverageDict'])
    
    def populateFrameListWidget(self, areas, frame = None):
        areas.sort()
        self.listWidget.clear()
        self.listWidget.addItems(areas)
        if frame:
            try:
                frameItem = self.listWidget.findItems(frame, Qt.MatchExactly)[0]
                self.listWidget.setCurrentItem(frameItem)
            except:
                pass
    
    def populateLists(self, setupDict):
        areasToList = setupDict.keys()
        linesToList = []
        for key in areasToList:
            lines = setupDict[key]
            for line in lines:
                if line not in linesToList:
                    linesToList.append(line)
        areasFromList = [area for area in self.areas if area not in areasToList]
        linesFromList = [line for line in self.lines if line not in linesToList]
        return linesFromList, linesToList, areasFromList, areasToList

    def checkDelimiters(self, setupDict):
        """
        Check delimiters
        """
        for i in range(self.treeWidget.invisibleRootItem().childCount()):
            areaItem = self.treeWidget.invisibleRootItem().child(i)
            for j in range(self.treeWidget.invisibleRootItem().child(i).childCount()):
                delimiterItem = areaItem.child(j)
                if areaItem.text(0) in setupDict.keys():
                    if delimiterItem.text(1) not in setupDict[areaItem.text(0)]:
                        delimiterItem.setCheckState(1,Qt.Unchecked)

    def loadJson(self, filename):
        """
        Loads a json file
        """
        filename = QFileDialog.getOpenFileName(self, self.tr('Open Field Setup configuration'), self.folder, self.tr('Earth Coverage Setup File (*.dsgearthcov)'))
        if not filename:
            return
        return self.readJsonFile(filename)

    def populateClasses(self):
        """
        Populates area classes
        """
        self.treeWidget.clear()
        selectedAreaClasses = self.areasCustomSelector.toLs
        for i in range(len(selectedAreaClasses)):
            treeItem = QtGui.QTreeWidgetItem()
            treeItem.setText(0,selectedAreaClasses[i])
            self.treeWidget.insertTopLevelItem(0,treeItem)

    def populateDelimiters(self):
        """
        Populates line classes (area delimiters)
        """
        delimiterList = []
        for i in range(self.linesCustomSelector.toList.__len__()):
            delimiterList.append(self.linesCustomSelector.toList.item(i).text())
        for i in range(self.treeWidget.invisibleRootItem().childCount()):
            for delimiter in delimiterList:
                treeItem = QtGui.QTreeWidgetItem(self.treeWidget.invisibleRootItem().child(i))
                treeItem.setText(1,delimiter)
                treeItem.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
                treeItem.setCheckState(1,Qt.Checked)
            self.treeWidget.invisibleRootItem().child(i).setExpanded(True)

    def getEarthCoverageDictFromTree(self):
        """
        Gets earth coverage configuration from the tree widget
        """
        invRootItem = self.treeWidget.invisibleRootItem()
        earthCoverageDict = dict()
        for i in range(invRootItem.childCount()):
            childClass = invRootItem.child(i)
            earthCoverageDict[childClass.text(0)] = []
            for j in range(childClass.childCount()):
                if childClass.child(j).checkState(1) == Qt.Checked:
                    earthCoverageDict[childClass.text(0)].append(childClass.child(j).text(1))
        return earthCoverageDict
    
    def buildDict(self):
        '''
        Gets earth coverage dict from interface
        '''
        self.configDict['edgvVersion'] = self.edgvVersion
        self.configDict['configName'] = self.nameLineEdit.text()
        self.configDict['frameLayer'] = self.listWidget.currentItem().text()
        self.configDict['earthCoverageDict'] = self.getEarthCoverageDictFromTree()

    def buildTree(self):
        """
        Builds the earth coverage tree using the selected areas and lines
        """
        self.populateClasses()
        self.populateDelimiters()
        self.treeWidget.expandAll()
        self.treeWidget.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.treeWidget.header().setStretchLastSection(False)
    
    def on_filterLineEdit_textChanged(self, text):
        """
        Filters the items to make it easier to spot and select them
        """
        classes = [edgvClass for edgvClass in self.areas if text in edgvClass]
        self.listWidget.clear()
        self.listWidget.addItems(classes)
        self.listWidget.sortItems()
    
    def validateEarthCoverageTreeWidget(self):
        rootNode = self.treeWidget.invisibleRootItem()
        childCount = rootNode.childCount()
        for i in range(childCount):
            areaItem = rootNode.child(i)
            lineChildCount = areaItem.childCount()
            hasSelected = False
            for j in range(lineChildCount):
                lineChild = areaItem.child(j)
                if lineChild.checkState(1) == Qt.Checked:
                    hasSelected = True
                    break
            if not hasSelected:
                return False
        return True

    def validateCurrentPage(self):
        if self.currentId() == 0:
            errorMsg = ''
            isValidated = True
            if self.nameLineEdit.text() == '':
                errorMsg += self.tr('An Earth Coverage name must be set.\n')
                isValidated = False
            if self.nameLineEdit.text() in self.propertyList:
                errorMsg += self.tr('An Earth Coverage with this name already exists.\n')
                isValidated = False
            if self.listWidget.currentRow() == -1:
                errorMsg += self.tr('A frame layer must be chosen.\n')
                isValidated = False
            if not isValidated:
                QMessageBox.warning(self, self.tr('Error!'), errorMsg)
            return isValidated
        elif self.currentId() == 1:
            if self.areasCustomSelector.toLs == []:
                errorMsg = self.tr('Areas must be chosen for Earth Coverage.\n')
                QMessageBox.warning(self, self.tr('Error!'), errorMsg)
                return False
            return True
        elif self.currentId() == 2:
            if self.linesCustomSelector.toLs == []:
                errorMsg = self.tr('Lines must be chosen for Earth Coverage.\n')
                QMessageBox.warning(self, self.tr('Error!'), errorMsg)
                return False
            return True
        elif self.currentId() == 3:
        #at least one line selected for each area
            if not self.validateEarthCoverageTreeWidget():
                errorMsg = self.tr('At least one line must be chosen for each Earth Coverage area.\n')
                QMessageBox.warning(self, self.tr('Error!'), errorMsg)
                return False
            return True
        else:
            return True
class DsgToolsOpInstaller(QObject):
    def __init__(self, iface, parent=None, parentMenu=None):
        """
        Constructor
        """
        super(DsgToolsOpInstaller, self).__init__()
        self.iface = iface
        self.parentMenu = parentMenu
        self.utils = Utils()
        self.parent = parent
        self.icon_path = ':/plugins/DsgTools/icons/militarySimbology.png'

    def createAuxFolder(self):
        """
        Creates a auxiliary foldes to unzip the installer zip file
        """
        # current path point to DsgToolsOp folder
        currentPath = os.path.abspath(os.path.dirname(__file__))
        # folder "auxiliar" inside DsgToolsOp folder
        auxFolder = os.path.join(currentPath, 'auxiliar')
        # creating and returning the folder
        os.makedirs(auxFolder)
        return auxFolder

    def deleteAuxFolder(self):
        """
        Deletes the auxiliar folder
        """
        # current path point to DsgToolsOp folder
        currentPath = os.path.abspath(os.path.dirname(__file__))
        # working on MilitaryTools folder
        auxPath = os.path.join(currentPath, 'auxiliar')
        shutil.rmtree(auxPath, ignore_errors=True)

    def uninstallDsgToolsOp(self):
        """
        Uninstall all folders and files created
        """
        parentUi = self.iface.mainWindow()
        if QMessageBox.question(
                parentUi, self.tr('Question'),
                self.
                tr('DsgToolsOp is going to be uninstalled. Would you like to continue?'
                   ),
                QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Cancel:
            return

        # current path point to DsgToolsOp folder
        currentPath = os.path.abspath(os.path.dirname(__file__))
        # working on MilitaryTools folder
        toolsPath = os.path.join(currentPath, 'MilitaryTools')
        for root, dirs, files in os.walk(toolsPath):
            # deleting directories from MilitaryTools folder
            for dir_ in dirs:
                top = os.path.join(currentPath, 'MilitaryTools', dir_)
                shutil.rmtree(top, ignore_errors=True)
            # deleting files (keeping __init__.py) files MilitaryTools folder
            for file_ in files:
                if file_ != '__init__.py':
                    os.remove(os.path.join(toolsPath, file_))

        QMessageBox.information(
            parentUi, self.tr('Success!'),
            self.tr('DsgToolsOp uninstalled successfully!'))

    def installDsgToolsOp(self, fullZipPath, parentUi=None):
        """
        Install files present into installer zip file
        :param fullZipPath: zip file path
        """
        try:
            reinstalled = False
            # current path point to DsgToolsOp folder
            currentPath = os.path.abspath(os.path.dirname(__file__))
            # creating auxiliar folder
            auxFolder = self.createAuxFolder()
            destFolder = os.path.join(currentPath, 'MilitaryTools')
            # unzipping files into Military tools
            self.unzipFiles(fullZipPath, auxFolder)
            # checking the zip file before installation
            if not self.checkZipFile():
                self.deleteAuxFolder()
                return
            # check if installed
            if self.checkIfInstalled():
                # if installed, get the version
                installedVersion = self.getInstalledVersion()
                # getting the version to be installed
                toBeInstalledVersion = self.getFilesVersion(auxFolder)
                # checks if the version to be installed is already installed
                if installedVersion == toBeInstalledVersion:
                    QMessageBox.warning(
                        parentUi, self.tr('Warning!'),
                        self.tr('DsgToolsOp version already installed!'))
                    self.deleteAuxFolder()
                    return
                # Checks if the version to be installed is lower
                if installedVersion > toBeInstalledVersion:
                    if QMessageBox.question(
                            parentUi, self.tr('Question'),
                            self.
                            tr('Selected version is lower than installed one. Would you like to continue?'
                               ), QMessageBox.Ok
                            | QMessageBox.Cancel) == QMessageBox.Cancel:
                        self.deleteAuxFolder()
                        return
                # deleting previous version files
                self.uninstallDsgToolsOp()
                reinstalled = True
            # copying files to destination folder
            self.copyFiles(auxFolder, destFolder)
            QMessageBox.information(
                parentUi, self.tr('Success!'),
                self.tr('DsgToolsOp installed successfully!'))
            if reinstalled:
                QMessageBox.warning(
                    parentUi, self.tr('Warning!'),
                    self.
                    tr('Please, reload QGIS to access the new installed version!'
                       ))
        except Exception as e:
            try:
                self.deleteAuxFolder()
            except:
                pass
            QMessageBox.critical(
                self.parentMenu, self.tr('Critical!'),
                self.tr('Problem installing DsgToolsOp: ') + '|'.join(e.args))

    def addUninstall(self, icon_path, parent, parentMenu):
        """
        Creates the uninstall action menu
        """
        action = parent.add_action(icon_path,
                                   text=parent.tr('DsgTools Op Uninstaller'),
                                   callback=parent.uninstallDsgToolsOp,
                                   parent=parentMenu,
                                   add_to_menu=False,
                                   add_to_toolbar=False)
        parentMenu.addAction(action)

    def unzipFiles(self, fullZipPath, auxFolder):
        """
        Unzips files inside a zip into a folder
        :param fullZipPath: zip file path
        :param auxFolder: unzip folder
        """
        zip = zipfile.ZipFile(fullZipPath)
        zip.extractall(auxFolder)
        currentPath = os.path.abspath(os.path.dirname(__file__))
        init = open(os.path.join(currentPath, 'auxiliar', '__init__.py'), 'w')
        init.close()

    def copyFiles(self, auxFolder, destFolder):
        """
        Copies all files to destination folder
        :param destFolder: destination folder
        :param auxFolder: source folder
        """
        for src_dir, dirs, files in os.walk(auxFolder):
            if 'expression' == src_dir.split(os.path.sep)[-1]:
                for file_ in files:
                    src_file = os.path.join(src_dir, file_)
                    expression_dir = os.path.join(
                        os.path.abspath(os.path.dirname(__file__)), '..', '..',
                        '..', 'expressions')
                    expression_file = os.path.join(expression_dir, file_)
                    if os.path.exists(expression_file):
                        os.remove(expression_file)
                    shutil.move(src_file, expression_dir)
            else:
                dst_dir = src_dir.replace(auxFolder, destFolder, 1)
                if not os.path.exists(dst_dir):
                    os.makedirs(dst_dir)
                for file_ in files:
                    src_file = os.path.join(src_dir, file_)
                    dst_file = os.path.join(dst_dir, file_)
                    if os.path.exists(dst_file):
                        os.remove(dst_file)
                    shutil.move(src_file, dst_dir)
        # deleting auxiliar folder
        self.copyToExpressions(auxFolder, destFolder)
        self.deleteAuxFolder()
        self.loadTools()

    def copyToExpressions(self, auxFolder, destFolder):
        pass

    def checkIfInstalled(self):
        """
        Checks if the files are already installed
        """
        installPath = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                   'MilitaryTools')
        w = os.walk(installPath).next()[2]
        # checking the number of files in the folder
        if len(w) <= 2:
            return False
        else:
            return True

    def getInstalledVersion(self):
        """
        Checks the current installed version
        """
        versionPath = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                   'MilitaryTools', 'dsgtoolsop_version.json')
        jsonDict = self.utils.readJsonFile(versionPath)
        return jsonDict['version']

    def getFilesVersion(self, filesFolder):
        """
        Gets the version to be installed
        """
        versionPath = os.path.join(filesFolder, 'dsgtoolsop_version.json')
        jsonDict = self.utils.readJsonFile(versionPath)
        return jsonDict['version']

    def checkZipFile(self):
        """
        Verifies the zip file prior to install
        """
        try:
            from DsgTools.DsgToolsOp.auxiliar.toolLoader import ToolLoader
            return True
        except Exception as e:
            QMessageBox.critical(
                self.parentMenu, self.tr('Critical!'),
                self.tr('Invalid Zip file: ') + '|'.join(e.args))
            return False

    def loadTools(self):
        """
        Loads the tools present in the installer zip file
        """
        try:
            self.toolList = []
            from DsgTools.DsgToolsOp.MilitaryTools.toolLoader import ToolLoader
            self.toolLoader = ToolLoader(self.parentMenu, self.parent,
                                         self.icon_path)
            self.toolLoader.loadTools()
            self.addUninstall(self.icon_path, self.parent, self.parentMenu)
        except Exception as e:
            QMessageBox.critical(
                self.parentMenu, self.tr('Critical!'),
                self.tr('Problem installing DsgToolsOp: ') + '|'.join(e.args))