void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds ) { sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } // insert log int layerId = getOrCreateLayerId( db, qgisLayerId ); for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it ) { if ( isAddedFeature( db, layerId, *it ) ) { // remove from added features log QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it ); sqlExec( db, sql ); } else { QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" ) .arg( layerId ) .arg( *it ); sqlExec( db, sql ); } } sqlite3_close( db ); }
bool Commands::before() { if (!m_mysqlSettings.before.isEmpty()) return sqlExec(m_mysqlSettings.before); return true; }
void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries ) { sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } // insert log int layerId = getOrCreateLayerId( db, qgisLayerId ); int commitNo = getCommitNo( db ); for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it ) { QgsFeatureId fid = it.key(); if ( isAddedFeature( db, layerId, fid ) ) { // skip added features continue; } QgsGeometry geom = it.value(); QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" ) .arg( layerId ) .arg( commitNo ) .arg( fid ) .arg( geom.exportToWkt() ); sqlExec( db, sql ); // TODO: use WKB instead of WKT? } increaseCommitNo( db ); sqlite3_close( db ); }
static void updateRecord(stKVDatabase *database, int64_t key, const void *value, int64_t sizeOfRecord) { MySqlDb *dbImpl = database->dbImpl; char *buf = sqlEscape(dbImpl, value, sizeOfRecord); sqlExec(dbImpl, "update %s set data=\"%s\" where id=%lld", dbImpl->table, buf, (long long)key); stSafeCFree(buf); }
void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures ) { sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } // insert log int layerId = getOrCreateLayerId( db, qgisLayerId ); // get new feature ids from db QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId ); QgsDataSourceURI uri = QgsDataSourceURI( layer->source() ); // only store feature ids QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() ); QList<int> newFeatureIds = sqlQueryInts( db, sql ); for ( int i = newFeatureIds.size() - 1; i >= 0; i-- ) { QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" ) .arg( layerId ) .arg( newFeatureIds.at( i ) ); sqlExec( db, sql ); } sqlite3_close( db ); }
void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes ) { sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } // insert log int layerId = getOrCreateLayerId( db, qgisLayerId ); int commitNo = getCommitNo( db ); for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it ) { QgsField field = *it; QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" ) .arg( layerId ) .arg( commitNo ) .arg( field.name() ) .arg( field.type() ) .arg( field.length() ) .arg( field.precision() ) .arg( field.comment() ); sqlExec( db, sql ); } increaseCommitNo( db ); sqlite3_close( db ); }
//移出指定组件列表从m_ServiceInfo并删除其数据库ServerDLLInfo里的记录 bool CServiceManage::RemoveDLLCom(CListCtrl *pListCtrl,SC_ServiceInfoStruct *pServiceInfo) { if(m_ServiceInfo.IsEmpty())//是否还有记录 { lstrcpy(m_szError,TEXT("组件列表记录里已没有任何组件")); return false; } //搜索要删除的记录 for(int i=0;i<= m_ServiceInfo.GetUpperBound();i++) { SC_ServiceInfoStruct* ptem; ptem = (SC_ServiceInfoStruct *)(m_ServiceInfo.GetAt(i)); //找到记录 if(ptem->uServiceID == pServiceInfo->uServiceID) { HANDLE hDatabase=sqlAddConnection("BZGameLocal.bcf"); if(hDatabase>(HANDLE)0x1000) { TCHAR szSQL[200]; wsprintf(szSQL,TEXT("DELETE FROM TGameServerInfo WHERE ServerInfoID = %d"), pServiceInfo->uServiceID); sqlExec(hDatabase,szSQL); } m_ServiceInfo.RemoveAt(i);//同时移出队列中的记录 pListCtrl->DeleteItem(i); return true; } } //没有记录 lstrcpy(m_szError,TEXT("列表中已无此组件记录")); return false; }
bool Commands::after() { if (!m_mysqlSettings.after.isEmpty()) return sqlExec(m_mysqlSettings.after); return true; }
static void removeRecord(stKVDatabase *database, int64_t key) { MySqlDb *dbImpl = database->dbImpl; sqlExec(dbImpl, "delete from %s where id=%lld", dbImpl->table, (long long)key); my_ulonglong numRows = mysql_affected_rows(dbImpl->conn); if (numRows == 0) { stThrowNew(ST_KV_DATABASE_EXCEPTION_ID, "remove of non-existent key %lld", (long long)key); } }
void QgsOfflineEditing::createLoggingTables( sqlite3* db ) { // indices QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)"; sqlExec( db, sql ); sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)"; sqlExec( db, sql ); sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)"; sqlExec( db, sql ); // layername <-> layer id sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)"; sqlExec( db, sql ); // offline fid <-> remote fid sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)"; sqlExec( db, sql ); // added attributes sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, "; sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)"; sqlExec( db, sql ); // added features sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)"; sqlExec( db, sql ); // removed features sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)"; sqlExec( db, sql ); // feature updates sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)"; sqlExec( db, sql ); // geometry updates sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)"; sqlExec( db, sql ); /* TODO: other logging tables - attr delete (not supported by SpatiaLite provider) */ }
void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) { // update fid lookup for added features // get remote added fids // NOTE: use QMap for sorted fids QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids; QgsFeature f; QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); emit progressModeSet( QgsOfflineEditing::ProcessFeatures, remoteLayer->featureCount() ); int i = 1; while ( fit.nextFeature( f ) ) { if ( offlineFid( db, layerId, f.id() ) == -1 ) { newRemoteFids[ f.id()] = true; } emit progressUpdated( i++ ); } // get local added fids // NOTE: fids are sorted QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); QList<int> newOfflineFids = sqlQueryInts( db, sql ); if ( newRemoteFids.size() != newOfflineFids.size() ) { //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) ); } else { // add new fid lookups i = 0; sqlExec( db, "BEGIN" ); for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it ) { addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() ); } sqlExec( db, "COMMIT" ); } }
void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) { // update fid lookup for added features // get remote added fids // NOTE: use QMap for sorted fids QMap < int, bool /*dummy*/ > newRemoteFids; QgsFeature f; remoteLayer->select( QgsAttributeList(), QgsRectangle(), false, false ); mProgressDialog->setupProgressBar( tr( "%v / %m features processed" ), remoteLayer->featureCount() ); int i = 1; while ( remoteLayer->nextFeature( f ) ) { if ( offlineFid( db, layerId, f.id() ) == -1 ) { newRemoteFids[ f.id()] = true; } mProgressDialog->setProgressValue( i++ ); } // get local added fids // NOTE: fids are sorted QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); QList<int> newOfflineFids = sqlQueryInts( db, sql ); if ( newRemoteFids.size() != newOfflineFids.size() ) { //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) ); } else { // add new fid lookups i = 0; sqlExec( db, "BEGIN" ); for ( QMap<int, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it ) { addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() ); } sqlExec( db, "COMMIT" ); } }
bool Commands::tag(const Image &img, const Tag &tag, bool after) { QString original = QString(tag.text()).replace(" ", "_"); QString command = after ? m_commandTagAfter : m_commandTagBefore; if (!command.isEmpty()) { Filename fn(command); fn.setEscapeMethod(&SqlWorker::escape); QStringList execs = fn.path(img, m_profile, QString(), 0, false, false, false, false, true); for (QString exec : execs) { exec.replace("%tag%", original) .replace("%original%", tag.text()) .replace("%type%", tag.type().name()) .replace("%number%", QString::number(tag.type().number())); log(QStringLiteral("Execution of \"%1\"").arg(exec)); Logger::getInstance().logCommand(exec); int code = QProcess::execute(exec); if (code != 0) log(QStringLiteral("Error executing command (return code: %1)").arg(code)); } } QString commandSql = after ? m_mysqlSettings.tagAfter : m_mysqlSettings.tagBefore; if (!commandSql.isEmpty()) { start(); Filename fn(commandSql); QStringList execs = fn.path(img, m_profile, QString(), 0, false, false, false, false, true); for (QString exec : execs) { exec.replace("%tag%", m_sqlWorker->escape(original)) .replace("%original%", m_sqlWorker->escape(tag.text())) .replace("%type%", m_sqlWorker->escape(tag.type().name())) .replace("%number%", QString::number(tag.type().number())); if (!sqlExec(exec)) return false; } } return true; }
int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId ) { QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId ); int layerId = sqlQueryInt( db, sql, -1 ); if ( layerId == -1 ) { // next layer id sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'"; int newLayerId = sqlQueryInt( db, sql, -1 ); // insert layer sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId ); sqlExec( db, sql ); // increase layer_id // TODO: use trigger for auto increment? sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 ); sqlExec( db, sql ); layerId = newLayerId; } return layerId; }
bool Commands::image(const Image &img, const QString &path) { // Normal commands if (!m_commandImage.isEmpty()) { Filename fn(m_commandImage); QStringList execs = fn.path(img, m_profile, QString(), 0, false, false, false, false); for (QString exec : execs) { exec.replace("%path:nobackslash%", QDir::toNativeSeparators(path).replace("\\", "/")) .replace("%path%", QDir::toNativeSeparators(path)); log(QStringLiteral("Execution of \"%1\"").arg(exec)); Logger::getInstance().logCommand(exec); int code = QProcess::execute(exec); if (code != 0) log(QStringLiteral("Error executing command (return code: %1)").arg(code)); } } // SQL commands if (!m_mysqlSettings.image.isEmpty()) { Filename fn(m_mysqlSettings.image); fn.setEscapeMethod(&SqlWorker::escape); QStringList execs = fn.path(img, m_profile, QString(), 0, false, false, false, false); for (QString exec : execs) { exec.replace("%path:nobackslash%", m_sqlWorker->escape(QDir::toNativeSeparators(path).replace("\\", "/"))) .replace("%path%", m_sqlWorker->escape(QDir::toNativeSeparators(path))); if (!sqlExec(exec)) return false; } } return true; }
void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap ) { sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } // insert log int layerId = getOrCreateLayerId( db, qgisLayerId ); int commitNo = getCommitNo( db ); for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit ) { QgsFeatureId fid = cit.key(); if ( isAddedFeature( db, layerId, fid ) ) { // skip added features continue; } QgsAttributeMap attrMap = cit.value(); for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it ) { QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" ) .arg( layerId ) .arg( commitNo ) .arg( fid ) .arg( it.key() ) // attr .arg( it.value().toString() ); // value sqlExec( db, sql ); } } increaseCommitNo( db ); sqlite3_close( db ); }
/* connect to a database server */ static MySqlDb *connect(stKVDatabaseConf *conf) { MySqlDb *dbImpl = stSafeCCalloc(sizeof(MySqlDb)); if ((dbImpl->conn = mysql_init(NULL)) == NULL) { disconnect(dbImpl); stThrowNew(ST_KV_DATABASE_EXCEPTION_ID, "mysql_init failed"); } if (mysql_real_connect(dbImpl->conn, stKVDatabaseConf_getHost(conf), stKVDatabaseConf_getUser(conf), stKVDatabaseConf_getPassword(conf), stKVDatabaseConf_getDatabaseName(conf), stKVDatabaseConf_getPort(conf), NULL, 0) == NULL) { stExcept *ex = createMySqlExcept(dbImpl, "failed to connect to MySql database: %s on %s as user %s", stKVDatabaseConf_getDatabaseName(conf), stKVDatabaseConf_getHost(conf), stKVDatabaseConf_getUser(conf)); disconnect(dbImpl); stThrow(ex); } dbImpl->table = stString_copy(stKVDatabaseConf_getTableName(conf)); // disable report of notes, so only warnings and errors come back sqlExec(dbImpl, "set sql_notes=0"); // set max sizes of an sql statment to 1G. This must also be specified // for the server by adding "max_allowed_packet = 1G" to the [mysqld] // section of my.cnf sqlExec(dbImpl, "set global max_allowed_packet=1073741824"); // set the idle timeout to a week int waitTimeout = 7 * 24 * 60 * 60; // 1 week sqlExec(dbImpl, "set wait_timeout=%d", waitTimeout); // set the read timeout to an hour int readTimeout = 60 * 60; // 1 hour sqlExec(dbImpl, "set net_read_timeout=%d", readTimeout); // NOTE: commit will not return an error, this does row-level locking on // the select done before the update sqlExec(dbImpl, "set autocommit = 0;"); sqlExec(dbImpl, "set session transaction isolation level serializable;"); return dbImpl; }
static void commitTransaction(stKVDatabase *database) { MySqlDb *dbImpl = database->dbImpl; sqlExec(dbImpl, "commit;"); }
void QgsOfflineEditing::increaseCommitNo( sqlite3* db ) { QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 ); sqlExec( db, sql ); }
static void startTransaction(stKVDatabase *database) { MySqlDb *dbImpl = database->dbImpl; sqlExec(dbImpl, "start transaction with consistent snapshot;"); }
static void insertInt64(stKVDatabase *database, int64_t key, int64_t value) { MySqlDb *dbImpl = database->dbImpl; char *buf = sqlEscape(dbImpl, &value, sizeof(int64_t)); sqlExec(dbImpl, "insert into %s (id, data) values (%lld, \"%s\")", dbImpl->table, (long long)key, buf); }
static void insertRecord(stKVDatabase *database, int64_t key, const void *value, int64_t sizeOfRecord) { MySqlDb *dbImpl = database->dbImpl; char *buf = sqlEscape(dbImpl, value, sizeOfRecord); sqlExec(dbImpl, "insert into %s (id, data) values (%lld, \"%s\")", dbImpl->table, (long long)key, buf); stSafeCFree(buf); }
static void updateInt64(stKVDatabase *database, int64_t key, int64_t value) { MySqlDb *dbImpl = database->dbImpl; char *buf = sqlEscape(dbImpl, &value, sizeof(int64_t)); sqlExec(dbImpl, "update %s set data=\"%s\" where id=%lld", dbImpl->table, buf, (long long)key); }
static void deleteDB(stKVDatabase *database) { MySqlDb *dbImpl = database->dbImpl; sqlExec(dbImpl, "drop table %s", dbImpl->table); destructDB(database); }
void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid ) { QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ); sqlExec( db, sql ); }
static void abortTransaction(stKVDatabase *database) { MySqlDb *dbImpl = database->dbImpl; sqlExec(dbImpl, "rollback;"); }
void QgsOfflineEditing::synchronize() { // open logging db sqlite3* db = openLoggingDb(); if ( db == NULL ) { return; } emit progressStarted(); // restore and sync remote layers QList<QgsMapLayer*> offlineLayers; QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers(); for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it ) { QgsMapLayer* layer = layer_it.value(); if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() ) { offlineLayers << layer; } } for ( int l = 0; l < offlineLayers.count(); l++ ) { QgsMapLayer* layer = offlineLayers[l]; emit layerProgressUpdated( l + 1, offlineLayers.count() ); QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString(); QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString(); QString remoteName = layer->name(); remoteName.remove( QRegExp( " \\(offline\\)$" ) ); QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider ); if ( remoteLayer->isValid() ) { // TODO: only add remote layer if there are log entries? QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer ); // copy style copySymbology( offlineLayer, remoteLayer ); // register this layer with the central layers registry QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true ); // apply layer edit log QString qgisLayerId = layer->id(); QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId ); int layerId = sqlQueryInt( db, sql, -1 ); if ( layerId != -1 ) { remoteLayer->startEditing(); // TODO: only get commitNos of this layer? int commitNo = getCommitNo( db ); for ( int i = 0; i < commitNo; i++ ) { // apply commits chronologically applyAttributesAdded( remoteLayer, db, layerId, i ); applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i ); applyGeometryChanges( remoteLayer, db, layerId, i ); } applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId ); applyFeaturesRemoved( remoteLayer, db, layerId ); if ( remoteLayer->commitChanges() ) { // update fid lookup updateFidLookup( remoteLayer, db, layerId ); // clear edit log for this layer sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId ); sqlExec( db, sql ); sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); sqlExec( db, sql ); sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId ); sqlExec( db, sql ); sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId ); sqlExec( db, sql ); sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId ); sqlExec( db, sql ); // reset commitNo QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" ); sqlExec( db, sql ); } else { showWarning( remoteLayer->commitErrors().join( "\n" ) ); } } // remove offline layer QgsMapLayerRegistry::instance()->removeMapLayers( ( QStringList() << qgisLayerId ) ); // disable offline project QString projectTitle = QgsProject::instance()->title(); projectTitle.remove( QRegExp( " \\(offline\\)$" ) ); QgsProject::instance()->title( projectTitle ); QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ); remoteLayer->reload(); //update with other changes } } emit progressStopped(); sqlite3_close( db ); }
/* create the keyword/value table */ static void createKVTable(MySqlDb *dbImpl) { sqlExec(dbImpl, "drop table if exists %s", dbImpl->table); sqlExec(dbImpl, "create table %s (id bigint primary key, data longblob) engine=INNODB;", dbImpl->table); }
void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath ) { if ( layer == NULL ) { return; } QString tableName = layer->name(); // create table QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName ); QString delim = ""; const QgsFields& fields = layer->dataProvider()->fields(); for ( int idx = 0; idx < fields.count(); ++idx ) { QString dataType = ""; QVariant::Type type = fields[idx].type(); if ( type == QVariant::Int ) { dataType = "INTEGER"; } else if ( type == QVariant::Double ) { dataType = "REAL"; } else if ( type == QVariant::String ) { dataType = "TEXT"; } else { showWarning( tr( "Unknown data type %1" ).arg( type ) ); } sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType ); delim = ","; } sql += ")"; // add geometry column QString geomType = ""; switch ( layer->wkbType() ) { case QGis::WKBPoint: geomType = "POINT"; break; case QGis::WKBMultiPoint: geomType = "MULTIPOINT"; break; case QGis::WKBLineString: geomType = "LINESTRING"; break; case QGis::WKBMultiLineString: geomType = "MULTILINESTRING"; break; case QGis::WKBPolygon: geomType = "POLYGON"; break; case QGis::WKBMultiPolygon: geomType = "MULTIPOLYGON"; break; default: showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) ); break; }; QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" ) .arg( tableName ) .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 ) .arg( geomType ); // create spatial index QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName ); int rc = sqlExec( db, sql ); if ( rc == SQLITE_OK ) { rc = sqlExec( db, sqlAddGeom ); if ( rc == SQLITE_OK ) { rc = sqlExec( db, sqlCreateIndex ); } } if ( rc == SQLITE_OK ) { // add new layer QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" ) .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" ); if ( newLayer->isValid() ) { // mark as offline layer newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true ); // store original layer source newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() ); newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() ); // copy style bool hasLabels = layer->hasLabelsEnabled(); if ( !hasLabels ) { // NOTE: copy symbology before adding the layer so it is displayed correctly copySymbology( layer, newLayer ); } // register this layer with the central layers registry QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer *>() << newLayer ); if ( hasLabels ) { // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND) copySymbology( layer, newLayer ); } // TODO: layer order // copy features newLayer->startEditing(); QgsFeature f; // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND) layer->setSubsetString( "" ); QgsFeatureIterator fit = layer->getFeatures(); emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->featureCount() ); int featureCount = 1; QList<QgsFeatureId> remoteFeatureIds; while ( fit.nextFeature( f ) ) { remoteFeatureIds << f.id(); // NOTE: Spatialite provider ignores position of geometry column // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND) int column = 0; QgsAttributes attrs = f.attributes(); QgsAttributes newAttrs( attrs.count() ); for ( int it = 0; it < attrs.count(); ++it ) { newAttrs[column++] = attrs[it]; } f.setAttributes( newAttrs ); newLayer->addFeature( f, false ); emit progressUpdated( featureCount++ ); } if ( newLayer->commitChanges() ) { emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->featureCount() ); featureCount = 1; // update feature id lookup int layerId = getOrCreateLayerId( db, newLayer->id() ); QList<QgsFeatureId> offlineFeatureIds; QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); while ( fit.nextFeature( f ) ) { offlineFeatureIds << f.id(); } // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature() sqlExec( db, "BEGIN" ); int remoteCount = remoteFeatureIds.size(); for ( int i = 0; i < remoteCount; i++ ) { addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) ); emit progressUpdated( featureCount++ ); } sqlExec( db, "COMMIT" ); } else { showWarning( newLayer->commitErrors().join( "\n" ) ); } // remove remote layer QgsMapLayerRegistry::instance()->removeMapLayers( QStringList() << layer->id() ); } } }