# -*- coding: utf-8 -*- """@package OsmUploadDlg Module provides simple way of uploading current OSM data. When performing edit operations all changed features are marked as 'U'=Updated. All new features are marked as 'A'=Added and all removed features are marked as 'R'=Removed. Uploader checks features statuses first. Then it creates HTTP connection. Upload is done in correct order so that data on OSM server always stay consistent. Upload phases and their exact order: - phases: 0.changeset creation, 1.nodes creation, 2.ways deletion, 3.ways update, 4.ways addition, 5.nodes deletion, 6.nodes update, 7.relation creation, 8.relation deletion, 9.relation update, 10.changeset closing """ from ui_OsmUploadDlg import Ui_OsmUploadDlg import OsmPlugin from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.QtNetwork import * from sys import * class OsmUploadDlg(QDialog, Ui_OsmUploadDlg): """Class provides simple way of uploading current OSM data. When performing edit operations all changed features are marked as 'U'=Updated. All new features are marked as 'A'=Added and all removed features are marked as 'R'=Removed. Uploader checks features statuses first. Then it creates HTTP connection. Upload is done in correct order so that data on OSM server always stay consistent. Upload phases and their exact order: - phases: 0.changeset creation, 1.nodes creation, 2.ways deletion, 3.ways update, 4.ways addition, 5.nodes deletion, 6.nodes update, 7.relation creation, 8.relation deletion, 9.relation update, 10.changeset closing """ def __init__(self,plugin): """The constructor. @param plugin is pointer to instance of OSM Plugin. """ QDialog.__init__(self,None) self.setupUi(self) self.dockWidget=plugin.dockWidget self.plugin=plugin self.dbm=plugin.dbm self.ur=plugin.undoredo self.urlHost = "api.openstreetmap.org" self.uploadButton = self.buttonBox.addButton(self.tr("Upload"), QDialogButtonBox.ActionRole) self.uploadChangesTable.setColumnCount(5) self.uploadChangesTable.setColumnWidth(0,80) self.uploadChangesTable.setColumnWidth(1,60) self.uploadChangesTable.setColumnWidth(2,60) self.uploadChangesTable.setColumnWidth(3,60) self.uploadChangesTable.setColumnWidth(4,60) self.uploadChangesTable.setHeaderLabels(["","Points","Lines","Polygons","Relations"]) key=QString(self.dbm.currentKey) p1=key.lastIndexOf(".") p2=key.lastIndexOf("/") key=key.mid(p2+1,p1-p2-1) self.groupBox.setTitle(QString("Changes in ").append(key)) item = QTreeWidgetItem(["Added","0","0","0","0"]) for i in range(1,5): item.setTextAlignment(i,Qt.AlignHCenter) self.uploadChangesTable.addTopLevelItem(item) item = QTreeWidgetItem(["Removed","0","0","0","0"]) for i in range(1,5): item.setTextAlignment(i,Qt.AlignHCenter) self.uploadChangesTable.addTopLevelItem(item) item = QTreeWidgetItem(["Changed","0","0","0","0"]) for i in range(1,5): item.setTextAlignment(i,Qt.AlignHCenter) self.uploadChangesTable.addTopLevelItem(item) self.userLineEdit.setFocus() self.passwdLineEdit.setEchoMode(QLineEdit.Password) self.uploadButton.setEnabled(False) self.pseudoId_map={} self.featureId_map={} self.qhttp_map={} self.progressDialog=QProgressDialog(self) self.progressDialog.setModal(True) self.finished=False self.httpRequestAborted=False self.savePasswd=False self.changesetId=None settings=QSettings() if settings.contains("/OSM_Plugin/uploadUser"): uplUser=settings.value("/OSM_Plugin/uploadUser",QVariant(QString())).toString() self.userLineEdit.setText(uplUser) if settings.contains("/OSM_Plugin/uploadPasswd"): uplPasswd=settings.value("/OSM_Plugin/uploadPasswd",QVariant(QString())).toString() self.passwdLineEdit.setText(uplPasswd) self.chkSavePasswd.setChecked(True) self.commentTextEdit.setFocus() # phases: 0.changeset creation, 1.nodes creation, 2.ways deletion, 3.ways update, # 4.ways addition, 5.nodes deletion, 6.nodes update, 7.relation creation, # 8.relation deletion, 9.relation update, 10.changeset closing self.phase=-1 self.cntPhases=11 self.cntActionsInPhase=[0]*self.cntPhases self.cntActionsInPhaseDone=0 self.cntActionsDone=0 self.createStatistics() if self.cntActionsAll<=2: # no action to upload (except for changeset opening and closing) self.commentTextEdit.setEnabled(False) self.accountGroupBox.setEnabled(False) self.showStatistics() self.__prepareDatabaseQueries() # connect signals self.connect(self.uploadButton, SIGNAL("clicked()"), self.uploadChanges) self.connect(self.userLineEdit, SIGNAL("textChanged(const QString &)"), self.enableUploadButton) self.connect(self.passwdLineEdit, SIGNAL("textChanged(const QString &)"), self.enableUploadButton) self.connect(self.chkShowPasswd, SIGNAL("clicked()"), self.__showPassword) self.connect(self.chkSavePasswd, SIGNAL("clicked()"), self.__savePassword) self.connect(self.progressDialog, SIGNAL("canceled()"), self.__cancelProgressDlg) self.enableUploadButton() # http connection self.http=QHttp(self) self.connect(self.http,SIGNAL("responseHeaderReceived(QHttpResponseHeader)"), self.__readResponseHeader) self.connect(self.http,SIGNAL("authenticationRequired(const QString &, quint16, QAuthenticator *)"), self.__authRequired) self.__setProxy() self.reqSetHost=self.http.setHost(self.urlHost, 80) self.httpRequestAborted = False self.httpRequestCanceled = False self.httpRequestZombie = False self.responseHeader="" # increase maximum recursion depth in python (__uploadStep is recursive function) setrecursionlimit(1000000) def uploadChanges(self): """Main function; starts the whole upload process. If checkbox for password saving was checked, password is stored to Quantum GIS settings. """ self.progressDialog.setWindowTitle(self.tr("OSM Upload")) self.progressDialog.setLabelText(self.tr("Uploading data...")) self.uploadButton.setEnabled(False) self.cursor=self.dbm.getConnection().cursor() settings=QSettings() settings.setValue("/OSM_Plugin/uploadUser",QVariant(self.userLineEdit.text())) if self.savePasswd: settings.setValue("/OSM_Plugin/uploadPasswd",QVariant(self.passwdLineEdit.text())) else: settings.remove("/OSM_Plugin/uploadPasswd") self.reqSetUser=self.http.setUser(self.userLineEdit.text(),self.passwdLineEdit.text()) # start upload with its first step (the next steps will follow automatically) self.__uploadStep() def __uploadStep(self): """Function calls the next step of uploading. """ # first update progressbar value self.progressDialog.setMaximum(self.cntActionsAll) self.progressDialog.setValue(self.cntActionsDone) # check if (in actual upload phase) number of done actions reach all actions that should be done; # if it does, switch actual upload phase to the next one in the order if (self.phase==-1) or (self.cntActionsInPhaseDone==self.cntActionsInPhase[self.phase]): self.dbm.commit() # commit actions performed in finished phase # search for first next phase in which some upload actions should be done self.phase+=1 while self.phase=self.cntPhases: self.cursor.close() # all upload phases were finished! self.__finishUpload() return # yes, it is ;) tell its number # print QString("Starting upload phase no.%1.").arg(self.phase) # if necessary set up database cursor if not self.phase in (0,10): self.cursor.execute(self.selectQuery[self.phase]) # no action has been done yet in running phase self.cntActionsInPhaseDone = 0 # just print common info # print QString("Running step %1 of upload phase %2.").arg(self.cntActionsInPhaseDone+1).arg(self.phase) # perform next action in running phase if self.phase not in (0,10): record = self.cursor.fetchone() if record == None: # strange situation, number of features ready to upload in actual phase was actually lower than it was signalized # by self.cntActionsAll; well, ignore this step and run the next one, that will start the next upload phase! self.cntActionsInPhaseDone = self.cntActionsInPhase[self.phase] self.__uploadStep() if self.phase == 0: # compulsory changeset creation self.__createChangeset() elif self.phase == 1: # nodes creation self.__uploadNodeAddition(record) elif self.phase == 2: # ways deletion self.__uploadWayDeletion(record) elif self.phase == 3: # ways update self.__uploadWayUpdate(record) elif self.phase == 4: # ways creation self.__uploadWayAddition(record) elif self.phase == 5: # nodes deletion self.__uploadNodeDeletion(record) elif self.phase == 6: # nodes update self.__uploadNodeUpdate(record) elif self.phase == 7: # relations creation self.__uploadRelationAddition(record) elif self.phase == 8: # relations deletion self.__uploadRelationDeletion(record) elif self.phase == 9: # relations update self.__uploadRelationUpdate(record) elif self.phase == 10: # compulsory changeset closing self.__closeChangeset() else: print "Program shouldn't reach this place! (2)" return def __showPassword(self): """Function to show hidden password on dialog box, so that user can verify if he has written it right. """ if self.chkShowPasswd.isChecked(): self.passwdLineEdit.setEchoMode(QLineEdit.Normal) else: self.passwdLineEdit.setEchoMode(QLineEdit.Password) def __savePassword(self): """Function to show hidden password on dialog box, so that user can verify if he has written it right. """ if self.chkSavePasswd.isChecked(): self.savePasswd=True else: self.savePasswd=False def createStatistics(self): """Function finds out number of steps for all upload phases. These statistics are found out with examining features' statuses. Results are stored in uploaders' member variables. """ c=self.dbm.getConnection().cursor() self.cntActionsAll = 0 # phases: 0.changeset creation, 1.nodes creation, 2.ways deletion, 3.ways update, # 4.ways addition, 5.nodes deletion, 6.nodes update, 7.relation creation, # 8.relation deletion, 9.relation update, 10.changeset closing c.execute("select count(*) from node where status='A' and u=1") for row in c: self.cntNodesCreated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='A' and closed=0 and u=1") for row in c: self.cntLinesCreated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='A' and closed=1 and u=1") for row in c: self.cntPolygonsCreated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from node where status='R' and u=1") for row in c: self.cntNodesRemoved = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='R' and closed=0 and u=1") for row in c: self.cntLinesRemoved = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='R' and closed=1 and u=1") for row in c: self.cntPolygonsRemoved = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from node where status='U' and u=1") for row in c: self.cntNodesUpdated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='U' and closed=0 and u=1") for row in c: self.cntLinesUpdated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from way where status='U' and closed=1 and u=1") for row in c: self.cntPolygonsUpdated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from relation where status='A' and u=1") for row in c: self.cntRelationsCreated = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from relation where status='R' and u=1") for row in c: self.cntRelationsRemoved = row[0] self.cntActionsAll += row[0] c.execute("select count(*) from relation where status='U' and u=1") for row in c: self.cntRelationsUpdated = row[0] self.cntActionsAll += row[0] self.cntActionsInPhase[0] = 1 self.cntActionsInPhase[1] = self.cntNodesCreated self.cntActionsInPhase[2] = self.cntLinesRemoved+self.cntPolygonsRemoved self.cntActionsInPhase[3] = self.cntLinesUpdated+self.cntPolygonsUpdated self.cntActionsInPhase[4] = self.cntLinesCreated+self.cntPolygonsCreated self.cntActionsInPhase[5] = self.cntNodesRemoved self.cntActionsInPhase[6] = self.cntNodesUpdated self.cntActionsInPhase[7] = self.cntRelationsCreated self.cntActionsInPhase[8] = self.cntRelationsRemoved self.cntActionsInPhase[9] = self.cntRelationsUpdated self.cntActionsInPhase[10] = 1 self.cntActionsAll += 2 c.close() def zeroStatistics(self): """Function cancels all statistics of uploader. """ for i in range(1,self.cntPhases-1): self.cntActionsInPhase[i] = 0 self.cntActionsAll = 2 self.cntNodesCreated = 0 self.cntLinesRemoved = 0 self.cntPolygonsRemoved = 0 self.cntLinesCreated = 0 self.cntPolygonsCreated = 0 self.cntNodesRemoved = 0 self.cntNodesUpdated = 0 self.cntLinesUpdated = 0 self.cntPolygonsUpdated = 0 self.cntRelationsCreated = 0 self.cntRelationsRemoved = 0 self.cntRelationsUpdated = 0 def showStatistics(self): """Function shows (statistics) counts of typical actions that are ready for upload. Showing them is realized on OSM upload dialog with appropriate table of statistics. """ # phases: 0.changeset creation, 1.nodes creation, 2.ways deletion, 3.ways addition, # 4.nodes deletion, 5.nodes update, 6.ways update, 7.relations addition, 8.relations deletion, # 9.relations update, 10.changeset closing self.uploadChangesTable.topLevelItem(0).setText(1,str(self.cntNodesCreated)) self.uploadChangesTable.topLevelItem(1).setText(1,str(self.cntNodesRemoved)) self.uploadChangesTable.topLevelItem(2).setText(1,str(self.cntNodesUpdated)) self.uploadChangesTable.topLevelItem(0).setText(2,str(self.cntLinesCreated)) self.uploadChangesTable.topLevelItem(1).setText(2,str(self.cntLinesRemoved)) self.uploadChangesTable.topLevelItem(2).setText(2,str(self.cntLinesUpdated)) self.uploadChangesTable.topLevelItem(0).setText(3,str(self.cntPolygonsCreated)) self.uploadChangesTable.topLevelItem(1).setText(3,str(self.cntPolygonsRemoved)) self.uploadChangesTable.topLevelItem(2).setText(3,str(self.cntPolygonsUpdated)) self.uploadChangesTable.topLevelItem(0).setText(4,str(self.cntRelationsCreated)) self.uploadChangesTable.topLevelItem(1).setText(4,str(self.cntRelationsRemoved)) self.uploadChangesTable.topLevelItem(2).setText(4,str(self.cntRelationsUpdated)) def __prepareDatabaseQueries(self): """Function prepares SQL queries for selecting objects to upload. Resulting array of queries is stored in member variable. """ self.selectQuery = [ # changeset creation "" # nodes creation ,"select n.id, n.lat, n.lon, n.user, n.timestamp, (select version_id \ from version where object_id=n.id and object_type='node') version_id from node n where n.status='A' and n.u=1" # ways deletion ,"select w.id, w.user, w.timestamp, (select version_id from version where \ object_id=w.id and object_type='way') version_id, w.closed from way w where w.status='R' and w.u=1" # ways update ,"select w.id, w.user, w.timestamp, (select version_id from version \ where object_id=w.id and object_type='way') version_id, w.closed from way w where w.status='U' and w.u=1" # ways creation ,"select w.id, w.user, w.timestamp, (select version_id from version \ where object_id=w.id and object_type='way') version_id, w.closed from way w where w.status='A' and w.u=1" # nodes deletion ,"select n.id, n.lat, n.lon, n.user, n.timestamp, (select version_id \ from version where object_id=n.id and object_type='node') version_id from node n where n.status='R' and n.u=1" # nodes update ,"select n.id, n.lat, n.lon, n.user, n.timestamp, (select version_id \ from version where object_id=n.id and object_type='node') version_id from node n where n.status='U' and n.u=1" # relations creation ,"select r.id, r.user, r.timestamp, (select version_id \ from version where object_id=r.id and object_type='relation') version_id from relation r where r.status='A' and r.u=1" # relations deletion ,"select r.id, r.user, r.timestamp, (select version_id \ from version where object_id=r.id and object_type='relation') version_id from relation r where r.status='R' and r.u=1" # relations update ,"select r.id, r.user, r.timestamp, (select version_id \ from version where object_id=r.id and object_type='relation') version_id from relation r where r.status='U' and r.u=1" # changeset closing ,"" ] def __uploadNodeAddition(self, nodeRecord): """Sends upload request for addition of new node. @param nodeRecord tuple object with information on node """ # create http connection with necessary authentication urlPath = "/api/0.6/node/create" # set http request's header header = QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # create http request's body (create XML with info about uploaded node) requestXml = self.createNodeXml(nodeRecord) # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeAdditionReqFinished) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) # remember http connection object and pseudoId, that was used # to node's identification in sqlite3 database self.qhttp_map[httpSessionId]=1 self.pseudoId_map[httpSessionId]=nodeRecord[0] def __uploadNodeUpdate(self, nodeRecord): """Sends upload request for updating one node. @param nodeRecord tuple object with information on node """ urlPath = QString("/api/0.6/node/%1").arg(nodeRecord[0]) # set http request's header header = QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # create http request's body (create XML with info about uploaded node) requestXml = self.createNodeXml(nodeRecord) # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeUpdateReqFinished) # send prepared request requestBytes=requestXml.toAscii() httpSessionId=self.http.request(header, requestBytes) # remember http connection object self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=nodeRecord[0] def __uploadNodeDeletion(self, nodeRecord): """Send upload request for deletion of one node. @param nodeRecord tuple object with information on node """ # create http connection with necessary authentication urlPath = QString("/api/0.6/node/%1").arg(nodeRecord[0]) # set http request's header header = QHttpRequestHeader("DELETE", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeDeletionReqFinished) requestXml=self.createNodeDelXml(nodeRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) # remember http connection object self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=nodeRecord[0] def createNodeXml(self, nodeRecord): """Function creates XML that will be added as http body to node addition request. @param nodeRecord tuple object with information on node """ requestXml = QString("") version = nodeRecord[5] # version_id requestXml.append(QString("")) requestXml.append(QString("None: requestXml.append(QString(" version=\"%1\"").arg(str(version))) requestXml.append(QString(" changeset=\"%1\">") .arg(str(self.changesetId)) ) # selecting tags to construct correct XML ct=self.dbm.getConnection().cursor() ct.execute("select key, val from tag where object_id=:nodeId and object_type=\"node\" and u=1",{"nodeId":nodeRecord[0]}) for tagRecord in ct: requestXml.append(QString("").arg(tagRecord[0]).arg(tagRecord[1])) ct.close() requestXml.append(QString("")) return requestXml def createNodeDelXml(self, nodeRecord): """Function creates XML that will be added as http body to node deletion request. @param nodeRecord tuple object with information on node """ requestXml = QString("") requestXml.append(QString("")) requestXml.append(QString("").arg(str(self.changesetId))) requestXml.append(QString("")) return requestXml def createWayXml(self, wayRecord): """Creates XML that have to be added as http body to way addition request. @param wayRecord tuple object with information on way """ pseudoWayId = wayRecord[0] requestXml = QString("") version = wayRecord[3] # version_id closed = wayRecord[4] requestXml.append(QString("")) requestXml.append(QString("None: requestXml.append(QString(" version=\"%1\"").arg(str(version))) requestXml.append(QString(" changeset=\"%1\">") .arg(str(self.changesetId)) ) # selecting way members to construct correct XML firstMember = None cwm=self.dbm.getConnection().cursor() cwm.execute("select node_id from way_member where way_id=:pseudoWayId and u=1",{"pseudoWayId": pseudoWayId}) for wayMemberRecord in cwm: if firstMember==None: firstMember=wayMemberRecord[0] requestXml.append(QString("").arg(wayMemberRecord[0])) cwm.close() if closed==1: tmp=None if firstMember: requestXml.append(QString("").arg(firstMember)) # selecting tags to construct correct XML ct=self.dbm.getConnection().cursor() ct.execute("select key, val from tag where object_id=:pseudoWayId and u=1",{"pseudoWayId": pseudoWayId}) for tagRecord in ct: tmp=None requestXml.append(QString("").arg(tagRecord[0]).arg(tagRecord[1])) ct.close() # finish XML construction requestXml.append(QString("")) return requestXml def __uploadWayAddition(self, wayRecord): """Sends upload request for addition of new way. @param wayRecord tuple object with information on way """ pseudoWayId = wayRecord[0] # create http connection with necessary authentication urlPath = "/api/0.6/way/create" # set http request's header header = QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayAdditionReqFinished) # create http request's body (create XML with info about uploaded way) requestXml = self.createWayXml(wayRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) # remember pseudoId, that was used to nodes identification in sqlite3 database self.qhttp_map[httpSessionId]=1 self.pseudoId_map[httpSessionId]=pseudoWayId def __uploadWayUpdate(self, wayRecord): """Send upload request for updating given way. @param wayRecord tuple object with information on way """ # create http connection with necessary authentication urlPath = QString("/api/0.6/way/%1").arg(wayRecord[0]) # set http request's header header=QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayUpdateReqFinished) # create http request's body (create XML with info about uploaded way) requestXml = self.createWayXml(wayRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=wayRecord[0] def __uploadWayDeletion(self, wayRecord): """Send upload request for removing given way. @param wayRecord tuple object with information on way """ # create http connection with necessary authentication urlPath = QString("/api/0.6/way/%1").arg(wayRecord[0]) # set http request's header header = QHttpRequestHeader("DELETE", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayDeletionReqFinished) # create http request's body (create XML with info about uploaded way deletion) requestXml = self.createWayXml(wayRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) # remember http connection object self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=wayRecord[0] def createRelationXml(self, relRecord): """Creates XML that will be added to relation addition/deletion/update http request. @param relRecord tuple object with information on relation """ pseudoRelId = relRecord[0] requestXml = QString("") version = relRecord[3] # version_id requestXml.append(QString("")) requestXml.append(QString("None: requestXml.append(QString(" version=\"%1\"").arg(str(version))) requestXml.append(QString(" changeset=\"%1\">").arg(str(self.changesetId))) # selecting way members to construct correct XML firstMember = None c=self.dbm.getConnection().cursor() c.execute("select member_type, role, member_id from relation_member where relation_id=:pseudoRelId and u=1",{"pseudoRelId": pseudoRelId}) for relMemberRecord in c: requestXml.append(QString("None: requestXml.append(QString(" role=\"%1\"").arg(relMemberRecord[1])) requestXml.append(QString(" ref=\"%1\"/>").arg(relMemberRecord[2])) c.close() # selecting tags to construct correct XML ct=self.dbm.getConnection().cursor() ct.execute("select key, val from tag where object_id=:pseudoRelId and object_type='relation' and u=1",{"pseudoRelId": pseudoRelId}) for tagRecord in ct: requestXml.append(QString("").arg(tagRecord[0]).arg(tagRecord[1])) ct.close() # finish XML construction requestXml.append(QString("")) return requestXml def __uploadRelationAddition(self, relRecord): """Sends upload request for addition of new relation. @param relRecord tuple object with information on relation """ pseudoRelId = relRecord[0] # create http connection with necessary authentication urlPath = "/api/0.6/relation/create" # set http request's header header = QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationAdditionReqFinished) # create http request's body (create XML with info about uploaded way) requestXml = self.createRelationXml(relRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) # remember pseudoId, that was used to nodes identification in sqlite3 database self.qhttp_map[httpSessionId]=1 self.pseudoId_map[httpSessionId]=pseudoRelId def __uploadRelationUpdate(self,relRecord): """Sends upload request for updating given relation. @param relRecord tuple object with information on relation """ # create http connection with necessary authentication urlPath = QString("/api/0.6/relation/%1").arg(relRecord[0]) # set http request's header header=QHttpRequestHeader("PUT", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationUpdateReqFinished) # create http request's body (create XML with info about uploaded way) requestXml = self.createRelationXml(relRecord) # send prepared request requestBytes = requestXml.toAscii() httpSessionId = self.http.request(header, requestBytes) self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=relRecord[0] def __uploadRelationDeletion(self, relRecord): """Function sends upload request for removing given relation. @param relRecord tuple object with information on relation """ # create http connection with necessary authentication urlPath = QString("/api/0.6/relation/%1").arg(relRecord[0]) # set http request's header header=QHttpRequestHeader("DELETE", urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationDeletionReqFinished) # create http request's body (create XML with info about uploaded way deletion) requestXml = self.createRelationXml(relRecord) # send prepared request requestBytes=requestXml.toAscii() httpSessionId=self.http.request(header, requestBytes) # remember http connection object self.qhttp_map[httpSessionId]=1 self.featureId_map[httpSessionId]=relRecord[0] def escape_html(self,text): """Function replaces chars that are problematic for html, such as < > & " ' with < > & " ' @param text text to replace problematic characters in @return transformed text """ text=text.replace(QString("&"), QString("&")); # must be first text=text.replace(QString("<"), QString("<")); text=text.replace(QString(">"), QString(">")); text=text.replace(QString("\""), QString(""")); text=text.replace(QString("'"), QString("'")); return text def __createChangeset(self): """Function sends request for creating new OSM changeset. Changeset creation has to be first upload action. Changeset closing has to be the last one. """ # create http connection with necessary authentication urlPath="/api/0.6/changeset/create" # set http request's header header=QHttpRequestHeader("PUT",urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentType("text/xml; charset=utf-8") # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpChangesetCreationReqFinished) # get user comment on upload actions userComment=self.commentTextEdit.toPlainText() userComment=self.escape_html(userComment) userCommentBytes=userComment.toUtf8() # create http request's body (create XML with info about uploaded way) requestXml=QString("\n\n\n\n\n") # send prepared request requestBytes=requestXml.toAscii() httpSessionId=self.http.request(header, requestBytes) # remember http connection object self.qhttp_map[httpSessionId]=1 def __closeChangeset(self): """Function sends request for closing opened OSM changeset. Changeset creation has to be first upload action. Changeset closing has to be the last one. """ # create http connection with necessary authentication urlPath = QString("/api/0.6/changeset/%1/close").arg(self.changesetId) # set http request's header header = QHttpRequestHeader("PUT",urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") header.setContentLength(0) # connect http response signals to appropriate functions self.connect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpChangesetClosingReqFinished) self.changesetId=None # send prepared request httpSessionId=self.http.request(header) # remember http connection object self.qhttp_map[httpSessionId]=1 def enableUploadButton(self): """Function finds out if it is possible to enable upload button. If yes, function just enables it, else it disables it. Following condition has to be satisfied to enable upload button: -There has to be something to upload. -Both user login and password has to be filled. """ user=self.userLineEdit.text() passwd=self.passwdLineEdit.text() if not self.finished and user<>"" and passwd<>"" and self.cntActionsAll>2: self.uploadButton.setEnabled(True) else: self.uploadButton.setEnabled(False) def __finishUpload(self): """This is function that runs after all edit actions changes are uploaded to OSM server. It clears edit changes tables, hide progress bar, clear statistics, clear undo/redo data, etc. """ self.finished = True self.progressDialog.hide() # delete database records with status='R' (Remove) self.dbm.removeUselessRecords() # show info that there are no more actions to upload self.zeroStatistics() self.showStatistics() # disable dialog items self.userLineEdit.setEnabled(False) self.passwdLineEdit.setEnabled(False) self.chkShowPasswd.setEnabled(False) self.accept() # clear undo/redo self.ur.clear() def cancelUpload(self,errMessage=None): """Function aborts the whole upload operation. In errMessage function gets the reason of upload canceling (error message). If OSM changeset was already opened while uploading, HTTP request to close it is send immediately. The HTTP connection of uploader is aborted, progress dialog is closed and the whole upload dialog is rejected. If there is some errMessage parameter, the message is shown to user. @param errMessage message to tell user after canceling upload """ if self.httpRequestAborted: return if self.changesetId: urlPath=QString("/api/0.6/changeset/%1/close").arg(self.changesetId) header=QHttpRequestHeader("PUT",urlPath,1,1) header.setValue("Host", self.urlHost) header.setValue("Connection", "keep-alive") httpSessionId=self.http.request(header) self.httpRequestAborted=True self.http.abort() self.uploadButton.setEnabled(True) self.disconnect(self.progressDialog, SIGNAL("canceled()"), self.__cancelProgressDlg) self.progressDialog.close() self.reject() if errMessage and errMessage<>"": QMessageBox.information(self,self.tr("OSM Upload"),errMessage) def __cancelProgressDlg(self): """This functions catches the signal emitted when clicking on Cancel button of progress dialog. It aborts opened HTTP connection and closes upload dialog. """ if self.httpRequestAborted: return self.httpRequestAborted=True self.http.abort() self.uploadButton.setEnabled(True) self.reject() ########################################################## # !!!!! HTTP RESPONSES FROM OPENSTREETMAP SERVER !!!!! # ########################################################## def __httpNodeAdditionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "node adition" is current uploader's phase. OSM server returns id that was assigned to uploaded node. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeAdditionReqFinished) nodePseudoId=self.pseudoId_map[requestId] del self.qhttp_map[requestId] del self.pseudoId_map[requestId] if error: self.cancelUpload( self.tr("Node addition failed.") ) return newNodeIdStr=QString(self.http.readAll().data()) newNodeId=(newNodeIdStr.toInt())[0] # update node's identification in sqlite3 database self.dbm.updateNodeId(nodePseudoId,newNodeId,self.userLineEdit.text()) self.dbm.recacheAffectedNow([(nodePseudoId,'Point'),(newNodeId,'Point')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpNodeUpdateReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "node updating" is current uploader's phase. OSM server returns id of new node's version. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeUpdateReqFinished) pointId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Node update failed.")) return newVersionIdStr=QString(self.http.readAll().data()) newVersionId=(newVersionIdStr.toInt())[0] self.dbm.updateVersionId(pointId,'node',newVersionId) self.dbm.updateUser(pointId,'node',self.userLineEdit.text()) self.dbm.changePointStatus(pointId,'U','N') self.dbm.recacheAffectedNow([(pointId,'Point')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpNodeDeletionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "node deletion" is current uploader's phase. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpNodeDeletionReqFinished) pointId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Node deletion failed.")) return self.dbm.removePointRecord(pointId) self.dbm.recacheAffectedNow([(pointId,'Point')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpWayAdditionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "way addition" is current uploader's phase. OSM server returns id that was assigned to uploaded way. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayAdditionReqFinished) wayPseudoId = self.pseudoId_map[requestId] del self.qhttp_map[requestId] del self.pseudoId_map[requestId] if error: self.cancelUpload(self.tr("Way addition failed.")) return newWayIdStr = QString(self.http.readAll().data()) newWayId = (newWayIdStr.toInt())[0] # update way identification in sqlite3 database self.dbm.updateWayId(wayPseudoId,newWayId,self.userLineEdit.text()) self.dbm.recacheAffectedNow([(wayPseudoId,'Line'),(newWayId,'Line'),(wayPseudoId,'Polygon'),(newWayId,'Polygon')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpWayUpdateReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "way updating" is current uploader's phase. OSM server returns id of new way's version. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayUpdateReqFinished) wayId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Way update failed.")) return newVersionIdStr=QString(self.http.readAll().data()) newVersionId=(newVersionIdStr.toInt())[0] self.dbm.updateVersionId(wayId,'way',newVersionId) self.dbm.updateUser(wayId,'way',self.userLineEdit.text()) self.dbm.changeWayStatus(wayId,'U','N') self.dbm.recacheAffectedNow([(wayId,'Line'),(wayId,'Polygon')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpWayDeletionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "way deletion" is current uploader's phase. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpWayDeletionReqFinished) wayId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Way deletion failed.")) return self.dbm.removeWayRecord(wayId) self.dbm.recacheAffectedNow([(wayId,'Line'),(wayId,'Polygon')]) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpRelationAdditionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "relation addition" is current uploader's phase. OSM server returns id that was assigned to uploaded relation. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationAdditionReqFinished) relPseudoId = self.pseudoId_map[requestId] del self.qhttp_map[requestId] del self.pseudoId_map[requestId] if error: self.cancelUpload(self.tr("Relation addition failed.")) return newRelIdStr=QString(self.http.readAll().data()) newRelId=(newRelIdStr.toInt())[0] # update way identification in sqlite3 database self.dbm.updateRelationId(relPseudoId,newRelId,self.userLineEdit.text()) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpRelationUpdateReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "relation updating" is current uploader's phase. OSM server returns id of new relation's version. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationUpdateReqFinished) relId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Relation update failed.")) return newVersionIdStr=QString(self.http.readAll().data()) newVersionId=(newVersionIdStr.toInt())[0] self.dbm.updateVersionId(relId,'relation',newVersionId) self.dbm.updateUser(relId,'relation',self.userLineEdit.text()) self.dbm.changeRelationStatus(relId,'U','N') # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpRelationDeletionReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "relation deletion" is current uploader's phase. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpRelationDeletionReqFinished) relId=self.featureId_map[requestId] del self.qhttp_map[requestId] del self.featureId_map[requestId] if error: self.cancelUpload(self.tr("Relation deletion failed.")) return self.dbm.removeRelationRecord(relId) # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpChangesetCreationReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "changeset creation" is current uploader's phase. OSM server returns id that was assigned to created changeset. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpChangesetCreationReqFinished) if error: self.cancelUpload(self.tr("Connection to OpenStreetMap server cannot be established. Please check your proxy settings, firewall settings and try again.")) return del self.qhttp_map[requestId] changesetIdStr=QString(self.http.readAll().data()) self.changesetId=(changesetIdStr.toInt())[0] # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __httpChangesetClosingReqFinished(self, requestId, error): """Function is called when requestFinished(...) signal is emitted on global HTTP connection object and "changeset closing" is current uploader's phase. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted: return if not requestId in self.qhttp_map: self.__examineResponse(requestId,error) return self.disconnect(self.http, SIGNAL("requestFinished(int, bool)"), self.__httpChangesetClosingReqFinished) if error: self.cancelUpload(self.tr("Changeset closing failed.")) return # call the next upload step self.cntActionsInPhaseDone+=1 self.cntActionsDone+=1 self.__uploadStep() def __readResponseHeader(self, responseHeader): """Function is called when responseHeaderReceived(...) signal is emitted on global HTTP connection object. If statusCode of responseHeader doesn't equal to 200, function cancels the whole connection. @param responseHeader header of HTTP response from the OSM server """ if responseHeader.statusCode() != 200: self.cancelUpload(self.tr("Upload process failed. OpenStreetMap server response: %1 - %2.") .arg(responseHeader.reasonPhrase()) .arg(responseHeader.value("Error"))) def __authRequired(self,s,a,b): """Function is called when authenticationRequired(...) signal is emitted on global HTTP connection object. We are really not interested in function parameters - we just cancel the connection. """ self.cancelUpload(self.tr("Authentication failed. Please try again with correct login and password.")) def __setProxy(self): """Function sets proxy to HTTP connection of uploader. HTTP connection object is not given in function parameter, because it's global - accessible for the whole uploader. """ # getting and setting proxy information settings=QSettings() proxyHost=QString() proxyUser=QString() proxyPassword=QString() proxyPort=0 proxyType=QNetworkProxy.NoProxy proxyEnabled=settings.value("proxy/proxyEnabled",QVariant(0)).toBool() if proxyEnabled: proxyHost=settings.value("proxy/proxyHost",QVariant("")).toString() proxyPort=settings.value("proxy/proxyPort",QVariant(8080)).toInt()[0] proxyUser=settings.value("proxy/proxyUser",QVariant("")).toString() proxyPassword=settings.value("proxy/proxyPassword",QVariant("")).toString() proxyTypeString=settings.value("proxy/proxyType",QVariant("")).toString() if proxyTypeString=="DefaultProxy": proxyType=QNetworkProxy.DefaultProxy elif proxyTypeString=="Socks5Proxy": proxyType=QNetworkProxy.Socks5Proxy elif proxyTypeString=="HttpProxy": proxyType=QNetworkProxy.HttpProxy elif proxyTypeString=="HttpCachingProxy": proxyType=QNetworkProxy.HttpCachingProxy elif proxyTypeString=="FtpCachingProxy": proxyType=QNetworkProxy.FtpCachingProxy self.proxy=QNetworkProxy() self.proxy.setType(proxyType) self.proxy.setHostName(proxyHost) self.proxy.setPort(proxyPort) self.reqSetProxy=self.http.__setProxy(self.proxy) def __examineResponse(self,requestId,error): """If error occured function examines requestId and cancel upload for appropriate reason. @param requestId identifier of http request @param error True if error occured on given request; False otherwise """ if self.httpRequestAborted or not error: return if requestId==self.reqSetHost: self.cancelUpload(self.tr("Setting host failed.")) elif requestId==self.reqSetUser: self.cancelUpload(self.tr("Setting user and password failed.")) elif requestId==self.reqSetProxy: self.cancelUpload(self.tr("Setting proxy failed."))