def importClicked(self): if self.repo is None: self.repo = repository.repos[self.repoCombo.currentIndex()] if self.layer is None: text = self.layerCombo.currentText() self.layer = resolveLayer(text) user, email = config.getUserInfo() if user is None: self.close() return message = self.messageBox.toPlainText() branch = self.branchCombo.currentText() try: self.repo.importgeopkg(self.layer, branch, message, user, email, False) filename, layername = namesFromLayer(self.layer) self.repo.checkoutlayer(filename, layername, ref = branch) self.layer.reload() self.layer.triggerRepaint() except GeoGigException as e: iface.messageBar().pushMessage("Error", str(e), level=QgsMessageBar.CRITICAL, duration=5) self.close() return addTrackedLayer(self.layer, self.repo.url) self.ok = True iface.messageBar().pushMessage("Layer was correctly added to repository", level=QgsMessageBar.INFO, duration=5) self.close()
def localChanges(self, layer): filename, layername = namesFromLayer(layer) con = sqlite3.connect(filename) cursor = con.cursor() attributes = [v[1] for v in cursor.execute("PRAGMA table_info('%s');" % layername)] attrnames = [a for a in attributes if a != "fid"] cursor.execute("SELECT * FROM %s_audit;" % layername) changes = cursor.fetchall() changesdict = {} tracking = getTrackingInfo(layer) repo = Repository(tracking.repoUrl) commitid = cursor.execute("SELECT commit_id FROM geogig_audited_tables WHERE table_name='%s';" % layername).fetchone()[0] geomField = cursor.execute("SELECT column_name FROM gpkg_geometry_columns WHERE table_name='%s';" % layername).fetchone()[0] for c in changes: featurechanges = {} path = str(c[attributes.index("fid")]) for attr in attrnames: if c[-1] == LOCAL_FEATURE_REMOVED: value = None else: if attr != geomField: value = c[attributes.index(attr)] else: request = QgsFeatureRequest().setFilterExpression("fid=%s" % path) features = list(layer.getFeatures(request)) if len(features) == 0: continue value = features[0].geometry().exportToWkt().upper() featurechanges[attr] = value path = geogigFidFromGpkgFid(tracking, path) changesdict[path] = LocalDiff(layername, path, repo, featurechanges, commitid, c[-1]) return changesdict
def revertChange(layer): if hasLocalChanges(layer): QMessageBox.warning(config.iface.mainWindow(), 'Cannot revert commit', "The layer has local changes.\n" "Revert local changes before reverting a previous commit.", QMessageBox.Ok) return tracking = getTrackingInfo(layer) repo = Repository(tracking.repoUrl) filename, layername = namesFromLayer(layer) from geogig.gui.dialogs.historyviewer import HistoryViewerDialog dlg = HistoryViewerDialog(repo, layername) dlg.exec_() if dlg.ref is not None: #TODO check that selected commit is in history line commit = Commit.fromref(repo, dlg.ref) # check if we are reverting commit which adds layer to the repo if commit.addsLayer(): QMessageBox.warning(config.iface.mainWindow(), 'Cannot revert commit', "Commits which add layer to the repository can not " "be reverted. Use GeoGig Navigator to remove layer " "from branch.") return applyLayerChanges(repo, layer, commit.commitid, commit.parent.commitid, False) layer.reload() layer.triggerRepaint() config.iface.messageBar().pushMessage("GeoGig", "Commit changes have been reverted in local layer", level=QgsMessageBar.INFO, duration=5) commitdialog.suggestedMessage = "Reverted changes from commit %s [%s] " % (commit.commitid, commit.message)
def updateFeatureIds(repo, layer, featureIds): filename, layername = namesFromLayer(layer) con = sqlite3.connect(filename) cursor = con.cursor() for ids in featureIds: cursor.execute('INSERT INTO "%s_fids" VALUES ("%s", "%s")' % (layername, ids[0], ids[1])) cursor.close() con.commit() con.close()
def getCommitId(layer): filename, layername = namesFromLayer(layer) con = sqlite3.connect(filename) cursor = con.cursor() cursor.execute( "SELECT commit_id FROM geogig_audited_tables WHERE table_name='%s';" % layername) commitid = cursor.fetchone()[0] cursor.close() con.close() return commitid
def importClicked(self): ret = QMessageBox.warning( config.iface.mainWindow(), 'Import warning', "Importing a layer will modify the original layer and might cause data loss.\n" "Make sure you have a backup copy of your layer before importing.\n" "Do you want to import the selected layer?", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.No: return if self.repo is None: self.repo = repository.repos[self.repoCombo.currentIndex()] if self.layer is None: text = self.layerCombo.currentText() self.layer = layerFromName(text) user, email = config.getUserInfo() if user is None: self.close() return message = self.messageBox.toPlainText() or datetime.now().strftime( "%Y-%m-%d %H_%M_%S") branch = self.branchCombo.currentText() try: self.repo.importgeopkg(self.layer, branch, message, user, email, False) filename, layername = namesFromLayer(self.layer) self.repo.checkoutlayer(filename, layername, ref=branch) self.layer.reload() self.layer.triggerRepaint() except GeoGigException as e: iface.messageBar().pushMessage("Error", str(e), level=QgsMessageBar.CRITICAL, duration=5) self.close() return addTrackedLayer(self.layer, self.repo.url) self.ok = True iface.messageBar().pushMessage( "Layer was correctly added to repository", level=QgsMessageBar.INFO, duration=5) self.close()
def addLayer(layer): if not layer.source().lower().split("|")[0].split(".")[-1] in [ "geopkg", "gpkg" ]: QMessageBox.warning( config.iface.mainWindow(), 'Cannot import layer', "Only geopackage layers are supported at the moment", QMessageBox.Ok) return filename, layername = namesFromLayer(layer) l = QgsVectorLayer(filename, 'tmp', 'ogr') # only single-layer per file are supported spatialLayers = 0 subLayers = l.dataProvider().subLayers() if len(subLayers) > 0: for lay in subLayers: tokens = lay.split(':') if len(tokens) > 4: tokens[1] += ":{}".format(tokens[2]) del tokens[2] elif len(tokens) == 4: if tokens[3] != "None": spatialLayers += 1 else: continue if spatialLayers > 1: QMessageBox.warning( config.iface.mainWindow(), 'Cannot import layer', "Only geopackage layers with single sublayer are supported at the moment", QMessageBox.Ok) return repos = repository.repos if repos: dlg = ImportDialog(config.iface.mainWindow(), layer=layer) dlg.exec_() if dlg.ok: setAsRepoLayer(layer) repoWatcher.repoChanged.emit(dlg.repo) else: QMessageBox.warning(config.iface.mainWindow(), 'Cannot import layer', "No repositories were found", QMessageBox.Ok)
def importClicked(self): ret = QMessageBox.warning(config.iface.mainWindow(), 'Import warning', "Importing a layer will modify the original layer and might cause data loss.\n" "Make sure you have a backup copy of your layer before importing.\n" "Do you want to import the selected layer?", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.No: return if self.repo is None: self.repo = repository.repos[self.repoCombo.currentIndex()] if self.layer is None: text = self.layerCombo.currentText() self.layer = layerFromName(text) user, email = config.getUserInfo() if user is None: self.close() return message = self.messageBox.toPlainText() or datetime.now().strftime("%Y-%m-%d %H_%M_%S") branch = self.branchCombo.currentText() try: self.repo.importgeopkg(self.layer, branch, message, user, email, False) filename, layername = namesFromLayer(self.layer) self.repo.checkoutlayer(filename, layername, ref = branch) self.layer.reload() self.layer.triggerRepaint() except GeoGigException as e: iface.messageBar().pushMessage("Error", str(e), level=QgsMessageBar.CRITICAL, duration=5) self.close() return addTrackedLayer(self.layer, self.repo.url) self.ok = True iface.messageBar().pushMessage("Layer was correctly added to repository", level=QgsMessageBar.INFO, duration=5) self.close()
def addLayer(layer): if not layer.source().lower().split("|")[0].split(".")[-1] in ["geopkg", "gpkg"]: QMessageBox.warning(config.iface.mainWindow(), 'Cannot import layer', "Only geopackage layers are supported at the moment", QMessageBox.Ok) return filename, layername = namesFromLayer(layer) l = QgsVectorLayer(filename, 'tmp', 'ogr') # only single-layer per file are supported spatialLayers = 0 subLayers = l.dataProvider().subLayers() if len(subLayers) > 0: for lay in subLayers: tokens = lay.split(':') if len(tokens) > 4: tokens[1] += ":{}".format(tokens[2]) del tokens[2] elif len(tokens) == 4: if tokens[3] != "None": spatialLayers += 1 else: continue if spatialLayers > 1: QMessageBox.warning(config.iface.mainWindow(), 'Cannot import layer', "Only geopackage layers with single sublayer are supported at the moment", QMessageBox.Ok) return repos = repository.repos if repos: dlg = ImportDialog(config.iface.mainWindow(), layer = layer) dlg.exec_() if dlg.ok: setAsRepoLayer(layer) repoWatcher.repoChanged.emit(dlg.repo) else: QMessageBox.warning(config.iface.mainWindow(), 'Cannot import layer', "No repositories were found", QMessageBox.Ok)
def revertChange(layer): tracking = getTrackingInfo(layer) repo = Repository(tracking.repoUrl) currentCommitId = getCommitId(layer) filename, layername = namesFromLayer(layer) dlg = CommitSelectDialog(repo, currentCommitId, layername) dlg.exec_() if dlg.ref is not None: #TODO check that selected commit is in history line applyLayerChanges(repo, layer, dlg.ref.commitid, dlg.ref.parent.commitid, False) layer.reload() layer.triggerRepaint() config.iface.messageBar().pushMessage( "GeoGig", "Version changes have been reverted in local layer", level=QgsMessageBar.INFO, duration=5) commitdialog.suggestedMessage = "Reverted changes from version %s [%s] " % ( dlg.ref.commitid, dlg.ref.message)
def revertChange(layer): if hasLocalChanges(layer): QMessageBox.warning( config.iface.mainWindow(), 'Cannot revert commit', "The layer has local changes.\n" "Revert local changes before reverting a previous commit.", QMessageBox.Ok) return tracking = getTrackingInfo(layer) repo = Repository(tracking.repoUrl) filename, layername = namesFromLayer(layer) from geogig.gui.dialogs.historyviewer import HistoryViewerDialog dlg = HistoryViewerDialog(repo, layername) dlg.exec_() if dlg.ref is not None: #TODO check that selected commit is in history line commit = Commit.fromref(repo, dlg.ref) # check if we are reverting commit which adds layer to the repo if commit.addsLayer(): QMessageBox.warning( config.iface.mainWindow(), 'Cannot revert commit', "Commits which add layer to the repository can not " "be reverted. Use GeoGig Navigator to remove layer " "from branch.") return applyLayerChanges(repo, layer, commit.commitid, commit.parent.commitid, False) layer.reload() layer.triggerRepaint() config.iface.messageBar().pushMessage( "GeoGig", "Commit changes have been reverted in local layer", level=QgsMessageBar.INFO, duration=5) commitdialog.suggestedMessage = "Reverted changes from commit %s [%s] " % ( commit.commitid, commit.message)
def syncLayer(layer): tracking = getTrackingInfo(layer) repo = Repository(tracking.repoUrl) filename, layername = namesFromLayer(layer) con = sqlite3.connect(filename) cursor = con.cursor() cursor.execute("SELECT * FROM %s_audit;" % layername) changes = bool(cursor.fetchall()) cursor.close() con.close() if changes: con = sqlite3.connect(filename) cursor = con.cursor() beforeAttrs = set(v[1] for v in cursor.execute("PRAGMA table_info('%s');" % layername)) afterAttrs = set( v[1] for v in cursor.execute("PRAGMA table_info('%s_audit');" % layername) if v[1] not in ["audit_timestamp", "audit_op"]) cursor.close() con.close() if beforeAttrs != afterAttrs: ret = QMessageBox.warning( iface.mainWindow(), "Cannot commit changes to repository", "The structure of attributes table has been modified.\n" "This type of change is not supported by GeoGig.", QMessageBox.Yes) return user, email = config.getUserInfo() if user is None: return dlg = CommitDialog(repo, layername) dlg.exec_() if dlg.branch is None: return if dlg.branch not in repo.branches(): commitId = getCommitId(layer) repo.createbranch(commitId, dlg.branch) mergeCommitId, importCommitId, conflicts, featureIds = repo.importgeopkg( layer, dlg.branch, dlg.message, user, email, True) if conflicts: ret = QMessageBox.warning( iface.mainWindow(), "Error while syncing", "There are conflicts between local and remote changes.\n" "Do you want to continue and fix them?", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.No: repo.closeTransaction(conflicts[0].transactionId) return solved, resolvedConflicts = solveConflicts(conflicts) if not solved: repo.closeTransaction(conflicts[0].transactionId) return for conflict, resolution in zip(conflicts, list(resolvedConflicts.values())): if resolution == ConflictDialog.LOCAL: conflict.resolveWithLocalVersion() elif resolution == ConflictDialog.REMOTE: conflict.resolveWithRemoteVersion() elif resolution == ConflictDialog.DELETE: conflict.resolveDeletingFeature() else: conflict.resolveWithNewFeature(resolution) repo.commitAndCloseMergeAndTransaction(user, email, "Resolved merge conflicts", conflicts[0].transactionId) updateFeatureIds(repo, layer, featureIds) try: applyLayerChanges(repo, layer, importCommitId, mergeCommitId) except: QgsMessageLog.logMessage( "Database locked while syncing. Using full layer checkout instead", level=QgsMessageLog.CRITICAL) repo.checkoutlayer(tracking.geopkg, layername, None, mergeCommitId) commitdialog.suggestedMessage = "" else: branches = [] for branch in repo.branches(): trees = repo.trees(branch) if layername in trees: branches.append(branch) branch, ok = QInputDialog.getItem(iface.mainWindow(), "Sync", "Select branch to update from", branches, 0, False) if not ok: return commitId = getCommitId(layer) headCommitId = repo.revparse(branch) applyLayerChanges(repo, layer, commitId, headCommitId) layer.reload() layer.triggerRepaint() repoWatcher.repoChanged.emit(repo) iface.messageBar().pushMessage("GeoGig", "Layer has been correctly synchronized", level=QgsMessageBar.INFO, duration=5) repoWatcher.layerUpdated.emit(layer)
def checkoutLayer(repo, layername, bbox, ref=None): ref = ref or repo.HEAD newCommitId = repo.revparse(ref) trackedlayer = getTrackingInfoForGeogigLayer(repo.url, layername) if trackedlayer is not None: if not os.path.exists(trackedlayer.geopkg): removeTrackedLayer(trackedlayer.source) trackedlayer = None filename = layerGeopackageFilename(layername, repo.title, repo.group) source = "%s|layername=%s" % (filename, layername) else: source = trackedlayer.source else: filename = layerGeopackageFilename(layername, repo.title, repo.group) source = "%s|layername=%s" % (filename, layername) if trackedlayer is None: repo.checkoutlayer(filename, layername, bbox, ref or repo.HEAD) addTrackedLayer(source, repo.url) try: layer = resolveLayerFromSource(source) iface.messageBar().pushMessage( "GeoGig", "Layer was already included in the current QGIS project", level=QgsMessageBar.INFO, duration=5) except WrongLayerSourceException: layer = loadLayerNoCrsDialog(source, layername, "ogr") QgsMapLayerRegistry.instance().addMapLayers([layer]) iface.messageBar().pushMessage("GeoGig", "Layer correctly added to project", level=QgsMessageBar.INFO, duration=5) elif ref is not None: currentCommitId = getCommitId(source) try: layer = resolveLayerFromSource(source) wasLoaded = True except WrongLayerSourceException: layer = loadLayerNoCrsDialog(source, layername, "ogr") wasLoaded = False if newCommitId != currentCommitId: if hasLocalChanges(layer): raise HasLocalChangesError() filename, layername = namesFromLayer(layer) repo.checkoutlayer(filename, layername, bbox, ref) layer.reload() if not wasLoaded: QgsMapLayerRegistry.instance().addMapLayers([layer]) iface.messageBar().pushMessage( "GeoGig", "Layer correctly added to project", level=QgsMessageBar.INFO, duration=5) else: iface.messageBar().pushMessage( "GeoGig", "Layer correctly updated to specified version", level=QgsMessageBar.INFO, duration=5) layer.triggerRepaint() else: if wasLoaded: iface.messageBar().pushMessage( "GeoGig", "Layer was already included in the current QGIS project", level=QgsMessageBar.INFO, duration=5) else: QgsMapLayerRegistry.instance().addMapLayers([layer]) iface.messageBar().pushMessage( "GeoGig", "Layer correctly added to the current QGIS project", level=QgsMessageBar.INFO, duration=5) repoWatcher.repoChanged.emit(repo) return layer
def applyLayerChanges(repo, layer, beforeCommitId, afterCommitId, clearAudit=True): layer.reload() filename, layername = namesFromLayer(layer) changesFilename = tempFilename("gpkg") beforeCommitId, afterCommitId = repo.revparse( beforeCommitId), repo.revparse(afterCommitId) repo.exportdiff(layername, beforeCommitId, afterCommitId, changesFilename) con = sqlite3.connect(filename) cursor = con.cursor() changesCon = sqlite3.connect(changesFilename) changesCursor = changesCon.cursor() attributes = [ v[1] for v in cursor.execute("PRAGMA table_info('%s');" % layername) ] attrnames = [a for a in attributes if a != "fid"] changesCursor.execute("SELECT * FROM %s_changes WHERE audit_op=2;" % layername) modified = changesCursor.fetchall() for m in modified: geogigfid = m[0] changesGpkgfid = gpkgfidFromGeogigfid(changesCursor, layername, geogigfid) gpkgfid = gpkgfidFromGeogigfid(cursor, layername, geogigfid) changesCursor.execute("SELECT * FROM %s WHERE fid='%s';" % (layername, changesGpkgfid)) featureRow = changesCursor.fetchone() attrs = { attr: featureRow[attributes.index(attr)] for attr in attrnames } vals = ",".join(["%s=?" % k for k in list(attrs.keys())]) cursor.execute( "UPDATE %s SET %s WHERE fid='%s'" % (layername, vals, gpkgfid), list(attrs.values())) changesCursor.execute("SELECT * FROM %s_changes WHERE audit_op=1;" % layername) added = changesCursor.fetchall() for a in added: geogigfid = a[0] changesGpkgfid = gpkgfidFromGeogigfid(changesCursor, layername, geogigfid) changesCursor.execute("SELECT * FROM %s WHERE fid='%s';" % (layername, changesGpkgfid)) featureRow = changesCursor.fetchone() attrs = { attr: featureRow[attributes.index(attr)] for attr in attrnames } cols = ', '.join('"%s"' % col for col in list(attrs.keys())) vals = ', '.join('?' for val in list(attrs.values())) cursor.execute( 'INSERT INTO "%s" (%s) VALUES (%s)' % (layername, cols, vals), list(attrs.values())) gpkgfid = cursor.lastrowid cursor.execute('INSERT INTO "%s_fids" VALUES ("%s", "%s")' % (layername, gpkgfid, geogigfid)) changesCursor.execute("SELECT * FROM %s_changes WHERE audit_op=3;" % layername) removed = changesCursor.fetchall() for r in removed: geogigfid = r[0] gpkgfid = gpkgfidFromGeogigfid(cursor, layername, geogigfid) cursor.execute("DELETE FROM %s WHERE fid='%s'" % (layername, gpkgfid)) changesCursor.close() changesCon.close() if clearAudit: cursor.execute("DELETE FROM %s_audit;" % layername) cursor.execute( "UPDATE geogig_audited_tables SET commit_id='%s' WHERE table_name='%s'" % (afterCommitId, layername)) con.commit() cursor.close() con.close()
def importgeopkg(self, layer, branch, message, authorName, authorEmail, interchange): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) filename, layername = namesFromLayer(layer) r = requests.get(self.url + "beginTransaction", params={"output_format": "json"}) r.raise_for_status() transactionId = r.json()["response"]["Transaction"]["ID"] self._checkoutbranch(branch, transactionId) payload = { "authorEmail": authorEmail, "authorName": authorName, "message": message, 'destPath': layername, "format": "gpkg", "transactionId": transactionId } # fix_print_with_import if interchange: payload["interchange"] = True filename = self.saveaudittables(filename, layername) files = { 'fileUpload': (os.path.basename(filename), open(filename, 'rb')) } encoder = MultipartEncoder(files) total = float(encoder.len) def callback(m): done = int(100 * m.bytes_read / total) iface.mainWindow().statusBar().showMessage( "Transferring geopkg to GeoGig server [{}%]".format(done)) monitor = MultipartEncoderMonitor(encoder, callback) r = requests.post(self.url + "import.json", params=payload, data=monitor, headers={'Content-Type': monitor.content_type}) self.__log(r.url, r.text, payload, "POST") r.raise_for_status() resp = r.json() taskId = resp["task"]["id"] checker = TaskChecker(self.rootUrl, taskId) loop = QEventLoop() checker.taskIsFinished.connect(loop.exit, Qt.QueuedConnection) checker.start() loop.exec_(flags=QEventLoop.ExcludeUserInputEvents) QApplication.restoreOverrideCursor() iface.mainWindow().statusBar().showMessage("") if not checker.ok and "error" in checker.response["task"]: errorMessage = checker.response["task"]["error"]["message"] raise GeoGigException("Cannot import layer: %s" % errorMessage) if interchange: try: nconflicts = checker.response["task"]["result"]["Merge"][ "conflicts"] except KeyError, e: nconflicts = 0 if nconflicts: mergeCommitId = self.HEAD importCommitId = checker.response["task"]["result"]["import"][ "importCommit"]["id"] ancestor = checker.response["task"]["result"]["Merge"][ "ancestor"] remote = checker.response["task"]["result"]["Merge"]["ours"] try: featureIds = checker.response["task"]["result"]["import"][ "NewFeatures"]["type"][0].get("ids", []) except: featureIds = [] con = sqlite3.connect(filename) cursor = con.cursor() geomField = cursor.execute( "SELECT column_name FROM gpkg_geometry_columns WHERE table_name='%s';" % layername).fetchone()[0] def _local(fid): cursor.execute( "SELECT gpkg_fid FROM %s_fids WHERE geogig_fid='%s';" % (layername, fid)) gpkgfid = int(cursor.fetchone()[0]) request = QgsFeatureRequest() request.setFilterFid(gpkgfid) try: feature = next(layer.getFeatures(request)) except: return None def _ensureNone(v): if v == NULL: return None else: return v local = { f.name(): _ensureNone(feature[f.name()]) for f in layer.pendingFields() } try: local[geomField] = feature.geometry().exportToWkt() except: local[geomField] = None return local conflicts = [] conflictsResponse = _ensurelist( checker.response["task"]["result"]["Merge"]["Feature"]) for c in conflictsResponse: if c["change"] == "CONFLICT": remoteFeatureId = c["ourvalue"] localFeatureId = c["theirvalue"] localFeature = _local(c["id"].split("/")[-1]) conflicts.append( ConflictDiff(self, c["id"], ancestor, remote, importCommitId, localFeature, localFeatureId, remoteFeatureId, transactionId)) cursor.close() con.close() else: #self._checkoutbranch("master", transactionId) self.closeTransaction(transactionId) mergeCommitId = checker.response["task"]["result"][ "newCommit"]["id"] importCommitId = checker.response["task"]["result"][ "importCommit"]["id"] try: featureIds = checker.response["task"]["result"][ "NewFeatures"]["type"][0].get("id", []) except: featureIds = [] conflicts = [] featureIds = [(f["provided"], f["assigned"]) for f in featureIds] return mergeCommitId, importCommitId, conflicts, featureIds