QGIS API Documentation  master-3f58142
src/core/qgsofflineediting.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002     offline_editing.cpp
00003 
00004     Offline Editing Plugin
00005     a QGIS plugin
00006      --------------------------------------
00007     Date                 : 22-Jul-2010
00008     Copyright            : (C) 2010 by Sourcepole
00009     Email                : info at sourcepole.ch
00010  ***************************************************************************
00011  *                                                                         *
00012  *   This program is free software; you can redistribute it and/or modify  *
00013  *   it under the terms of the GNU General Public License as published by  *
00014  *   the Free Software Foundation; either version 2 of the License, or     *
00015  *   (at your option) any later version.                                   *
00016  *                                                                         *
00017  ***************************************************************************/
00018 
00019 
00020 #include <qgsapplication.h>
00021 #include <qgsdatasourceuri.h>
00022 #include <qgsgeometry.h>
00023 #include <qgsmaplayer.h>
00024 #include <qgsmaplayerregistry.h>
00025 #include <qgsofflineediting.h>
00026 #include <qgsproject.h>
00027 #include <qgsvectordataprovider.h>
00028 
00029 #include <QDir>
00030 #include <QDomDocument>
00031 #include <QDomNode>
00032 #include <QFile>
00033 #include <QMessageBox>
00034 
00035 extern "C"
00036 {
00037 #include <sqlite3.h>
00038 #include <spatialite.h>
00039 }
00040 
00041 // TODO: DEBUG
00042 #include <QDebug>
00043 // END
00044 
00045 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
00046 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
00047 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
00048 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
00049 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
00050 
00051 QgsOfflineEditing::QgsOfflineEditing()
00052 {
00053   connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
00054 }
00055 
00056 QgsOfflineEditing::~QgsOfflineEditing()
00057 {
00058 }
00059 
00064 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds )
00065 {
00066   if ( layerIds.isEmpty() )
00067   {
00068     return false;
00069   }
00070   QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
00071   if ( createSpatialiteDB( dbPath ) )
00072   {
00073     spatialite_init( 0 );
00074     sqlite3* db;
00075     int rc = sqlite3_open( dbPath.toStdString().c_str(), &db );
00076     if ( rc != SQLITE_OK )
00077     {
00078       showWarning( tr( "Could not open the spatialite database" ) );
00079     }
00080     else
00081     {
00082       // create logging tables
00083       createLoggingTables( db );
00084 
00085       emit progressStarted();
00086 
00087       // copy selected vector layers to SpatiaLite
00088       for ( int i = 0; i < layerIds.count(); i++ )
00089       {
00090         emit layerProgressUpdated( i + 1, layerIds.count() );
00091 
00092         QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
00093         copyVectorLayer( qobject_cast<QgsVectorLayer*>( layer ), db, dbPath );
00094       }
00095 
00096       emit progressStopped();
00097 
00098       sqlite3_close( db );
00099 
00100       // save offline project
00101       QString projectTitle = QgsProject::instance()->title();
00102       if ( projectTitle.isEmpty() )
00103       {
00104         projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
00105       }
00106       projectTitle += " (offline)";
00107       QgsProject::instance()->title( projectTitle );
00108 
00109       QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, dbPath );
00110 
00111       return true;
00112     }
00113   }
00114 
00115   return false;
00116 
00117   // Workflow:
00118   // copy layers to spatialite
00119   // create spatialite db at offlineDataPath
00120   // create table for each layer
00121   // add new spatialite layer
00122   // copy features
00123   // save as offline project
00124   // mark offline layers
00125   // remove remote layers
00126   // mark as offline project
00127 }
00128 
00129 bool QgsOfflineEditing::isOfflineProject()
00130 {
00131   return !QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ).isEmpty();
00132 }
00133 
00134 void QgsOfflineEditing::synchronize()
00135 {
00136   // open logging db
00137   sqlite3* db = openLoggingDb();
00138   if ( db == NULL )
00139   {
00140     return;
00141   }
00142 
00143   emit progressStarted();
00144 
00145   // restore and sync remote layers
00146   QList<QgsMapLayer*> offlineLayers;
00147   QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
00148   for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
00149   {
00150     QgsMapLayer* layer = layer_it.value();
00151     if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
00152     {
00153       offlineLayers << layer;
00154     }
00155   }
00156 
00157   for ( int l = 0; l < offlineLayers.count(); l++ )
00158   {
00159     QgsMapLayer* layer = offlineLayers[l];
00160 
00161     emit layerProgressUpdated( l + 1, offlineLayers.count() );
00162 
00163     QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
00164     QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
00165     QString remoteName = layer->name();
00166     remoteName.remove( QRegExp( " \\(offline\\)$" ) );
00167 
00168     QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
00169     if ( remoteLayer->isValid() )
00170     {
00171       // TODO: only add remote layer if there are log entries?
00172 
00173       QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );
00174 
00175       // copy style
00176       copySymbology( offlineLayer, remoteLayer );
00177 
00178       // register this layer with the central layers registry
00179       QgsMapLayerRegistry::instance()->addMapLayers(
00180         QList<QgsMapLayer *>() << remoteLayer, true );
00181 
00182       // apply layer edit log
00183       QString qgisLayerId = layer->id();
00184       QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
00185       int layerId = sqlQueryInt( db, sql, -1 );
00186       if ( layerId != -1 )
00187       {
00188         remoteLayer->startEditing();
00189 
00190         // TODO: only get commitNos of this layer?
00191         int commitNo = getCommitNo( db );
00192         for ( int i = 0; i < commitNo; i++ )
00193         {
00194           // apply commits chronologically
00195           applyAttributesAdded( remoteLayer, db, layerId, i );
00196           applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
00197           applyGeometryChanges( remoteLayer, db, layerId, i );
00198         }
00199 
00200         applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
00201         applyFeaturesRemoved( remoteLayer, db, layerId );
00202 
00203         if ( remoteLayer->commitChanges() )
00204         {
00205           // update fid lookup
00206           updateFidLookup( remoteLayer, db, layerId );
00207 
00208           // clear edit log for this layer
00209           sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
00210           sqlExec( db, sql );
00211           sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
00212           sqlExec( db, sql );
00213           sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
00214           sqlExec( db, sql );
00215           sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
00216           sqlExec( db, sql );
00217           sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
00218           sqlExec( db, sql );
00219 
00220           // reset commitNo
00221           QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
00222           sqlExec( db, sql );
00223         }
00224         else
00225         {
00226           showWarning( remoteLayer->commitErrors().join( "\n" ) );
00227         }
00228       }
00229 
00230       // remove offline layer
00231       QgsMapLayerRegistry::instance()->removeMapLayers(
00232         ( QStringList() << qgisLayerId ) );
00233 
00234       // disable offline project
00235       QString projectTitle = QgsProject::instance()->title();
00236       projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
00237       QgsProject::instance()->title( projectTitle );
00238       QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
00239       remoteLayer->reload(); //update with other changes
00240     }
00241   }
00242 
00243   emit progressStopped();
00244 
00245   sqlite3_close( db );
00246 }
00247 
00248 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
00249 {
00250 // attempting to perform self-initialization for a newly created DB
00251   int ret;
00252   char sql[1024];
00253   char *errMsg = NULL;
00254   int count = 0;
00255   int i;
00256   char **results;
00257   int rows;
00258   int columns;
00259 
00260   if ( sqlite_handle == NULL )
00261     return;
00262   // checking if this DB is really empty
00263   strcpy( sql, "SELECT Count(*) from sqlite_master" );
00264   ret = sqlite3_get_table( sqlite_handle, sql, &results, &rows, &columns, NULL );
00265   if ( ret != SQLITE_OK )
00266     return;
00267   if ( rows < 1 )
00268     ;
00269   else
00270   {
00271     for ( i = 1; i <= rows; i++ )
00272       count = atoi( results[( i * columns ) + 0] );
00273   }
00274   sqlite3_free_table( results );
00275 
00276   if ( count > 0 )
00277     return;
00278 
00279   // all right, it's empty: proceding to initialize
00280   strcpy( sql, "SELECT InitSpatialMetadata()" );
00281   ret = sqlite3_exec( sqlite_handle, sql, NULL, NULL, &errMsg );
00282   if ( ret != SQLITE_OK )
00283   {
00284     QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
00285     errCause += QString::fromUtf8( errMsg );
00286     showWarning( errCause );
00287     sqlite3_free( errMsg );
00288     return;
00289   }
00290   spatial_ref_sys_init( sqlite_handle, 0 );
00291 }
00292 
00293 bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath )
00294 {
00295   int ret;
00296   sqlite3 *sqlite_handle;
00297   char *errMsg = NULL;
00298   QFile newDb( offlineDbPath );
00299   if ( newDb.exists() )
00300   {
00301     QFile::remove( offlineDbPath );
00302   }
00303 
00304   // see also QgsNewSpatialiteLayerDialog::createDb()
00305 
00306   QFileInfo fullPath = QFileInfo( offlineDbPath );
00307   QDir path = fullPath.dir();
00308 
00309   // Must be sure there is destination directory ~/.qgis
00310   QDir().mkpath( path.absolutePath( ) );
00311 
00312   // creating/opening the new database
00313   QString dbPath = newDb.fileName();
00314   spatialite_init( 0 );
00315   ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
00316   if ( ret )
00317   {
00318     // an error occurred
00319     QString errCause = tr( "Could not create a new database\n" );
00320     errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
00321     sqlite3_close( sqlite_handle );
00322     showWarning( errCause );
00323     return false;
00324   }
00325   // activating Foreign Key constraints
00326   ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
00327   if ( ret != SQLITE_OK )
00328   {
00329     showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
00330     sqlite3_free( errMsg );
00331     sqlite3_close( sqlite_handle );
00332     return false;
00333   }
00334   initializeSpatialMetadata( sqlite_handle );
00335 
00336   // all done: closing the DB connection
00337   sqlite3_close( sqlite_handle );
00338 
00339   return true;
00340 }
00341 
00342 void QgsOfflineEditing::createLoggingTables( sqlite3* db )
00343 {
00344   // indices
00345   QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
00346   sqlExec( db, sql );
00347 
00348   sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
00349   sqlExec( db, sql );
00350 
00351   sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
00352   sqlExec( db, sql );
00353 
00354   // layername <-> layer id
00355   sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
00356   sqlExec( db, sql );
00357 
00358   // offline fid <-> remote fid
00359   sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
00360   sqlExec( db, sql );
00361 
00362   // added attributes
00363   sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
00364   sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
00365   sqlExec( db, sql );
00366 
00367   // added features
00368   sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
00369   sqlExec( db, sql );
00370 
00371   // removed features
00372   sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
00373   sqlExec( db, sql );
00374 
00375   // feature updates
00376   sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
00377   sqlExec( db, sql );
00378 
00379   // geometry updates
00380   sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
00381   sqlExec( db, sql );
00382 
00383   /* TODO: other logging tables
00384     - attr delete (not supported by SpatiaLite provider)
00385   */
00386 }
00387 
00388 void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
00389 {
00390   if ( layer == NULL )
00391   {
00392     return;
00393   }
00394 
00395   QString tableName = layer->name();
00396 
00397   // create table
00398   QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
00399   QString delim = "";
00400   const QgsFields& fields = layer->dataProvider()->fields();
00401   for ( int idx = 0; idx < fields.count(); ++idx )
00402   {
00403     QString dataType = "";
00404     QVariant::Type type = fields[idx].type();
00405     if ( type == QVariant::Int )
00406     {
00407       dataType = "INTEGER";
00408     }
00409     else if ( type == QVariant::Double )
00410     {
00411       dataType = "REAL";
00412     }
00413     else if ( type == QVariant::String )
00414     {
00415       dataType = "TEXT";
00416     }
00417     else
00418     {
00419       showWarning( tr( "Unknown data type %1" ).arg( type ) );
00420     }
00421 
00422     sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
00423     delim = ",";
00424   }
00425   sql += ")";
00426 
00427   // add geometry column
00428   QString geomType = "";
00429   switch ( layer->wkbType() )
00430   {
00431     case QGis::WKBPoint:
00432       geomType = "POINT";
00433       break;
00434     case QGis::WKBMultiPoint:
00435       geomType = "MULTIPOINT";
00436       break;
00437     case QGis::WKBLineString:
00438       geomType = "LINESTRING";
00439       break;
00440     case QGis::WKBMultiLineString:
00441       geomType = "MULTILINESTRING";
00442       break;
00443     case QGis::WKBPolygon:
00444       geomType = "POLYGON";
00445       break;
00446     case QGis::WKBMultiPolygon:
00447       geomType = "MULTIPOLYGON";
00448       break;
00449     default:
00450       showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
00451       break;
00452   };
00453   QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
00454                        .arg( tableName )
00455                        .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
00456                        .arg( geomType );
00457 
00458   // create spatial index
00459   QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
00460 
00461   int rc = sqlExec( db, sql );
00462   if ( rc == SQLITE_OK )
00463   {
00464     rc = sqlExec( db, sqlAddGeom );
00465     if ( rc == SQLITE_OK )
00466     {
00467       rc = sqlExec( db, sqlCreateIndex );
00468     }
00469   }
00470 
00471   if ( rc == SQLITE_OK )
00472   {
00473     // add new layer
00474     QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" )
00475         .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" );
00476     if ( newLayer->isValid() )
00477     {
00478       // mark as offline layer
00479       newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true );
00480 
00481       // store original layer source
00482       newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() );
00483       newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() );
00484 
00485       // copy style
00486       bool hasLabels = layer->hasLabelsEnabled();
00487       if ( !hasLabels )
00488       {
00489         // NOTE: copy symbology before adding the layer so it is displayed correctly
00490         copySymbology( layer, newLayer );
00491       }
00492 
00493       // register this layer with the central layers registry
00494       QgsMapLayerRegistry::instance()->addMapLayers(
00495         QList<QgsMapLayer *>() << newLayer );
00496 
00497       if ( hasLabels )
00498       {
00499         // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
00500         copySymbology( layer, newLayer );
00501       }
00502 
00503       // TODO: layer order
00504 
00505       // copy features
00506       newLayer->startEditing();
00507       QgsFeature f;
00508 
00509       // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
00510       layer->setSubsetString( "" );
00511 
00512       QgsFeatureIterator fit = layer->getFeatures();
00513 
00514       emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->featureCount() );
00515       int featureCount = 1;
00516 
00517       QList<QgsFeatureId> remoteFeatureIds;
00518       while ( fit.nextFeature( f ) )
00519       {
00520         remoteFeatureIds << f.id();
00521 
00522         // NOTE: Spatialite provider ignores position of geometry column
00523         // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
00524         int column = 0;
00525         QgsAttributes newAttrs;
00526         QgsAttributes attrs = f.attributes();
00527         for ( int it = 0; it < attrs.count(); ++it )
00528         {
00529           newAttrs[column++] = attrs[it];
00530         }
00531         f.setAttributes( newAttrs );
00532 
00533         newLayer->addFeature( f, false );
00534 
00535         emit progressUpdated( featureCount++ );
00536       }
00537       if ( newLayer->commitChanges() )
00538       {
00539         emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->featureCount() );
00540         featureCount = 1;
00541 
00542         // update feature id lookup
00543         int layerId = getOrCreateLayerId( db, newLayer->id() );
00544         QList<QgsFeatureId> offlineFeatureIds;
00545 
00546         QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
00547         while ( fit.nextFeature( f ) )
00548         {
00549           offlineFeatureIds << f.id();
00550         }
00551 
00552         // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
00553         sqlExec( db, "BEGIN" );
00554         for ( int i = 0; i < remoteFeatureIds.size(); i++ )
00555         {
00556           addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
00557 
00558           emit progressUpdated( featureCount++ );
00559         }
00560         sqlExec( db, "COMMIT" );
00561       }
00562       else
00563       {
00564         showWarning( newLayer->commitErrors().join( "\n" ) );
00565       }
00566 
00567       // remove remote layer
00568       QgsMapLayerRegistry::instance()->removeMapLayers(
00569         QStringList() << layer->id() );
00570     }
00571   }
00572 }
00573 
00574 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
00575 {
00576   QString sql = QString( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
00577   QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
00578 
00579   const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
00580   QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
00581 
00582   // NOTE: uses last matching QVariant::Type of nativeTypes
00583   QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
00584   for ( int i = 0; i < nativeTypes.size(); i++ )
00585   {
00586     QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
00587     typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
00588   }
00589 
00590   emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
00591 
00592   for ( int i = 0; i < fields.size(); i++ )
00593   {
00594     // lookup typename from layer provider
00595     QgsField field = fields[i];
00596     if ( typeNameLookup.contains( field.type() ) )
00597     {
00598       QString typeName = typeNameLookup[ field.type()];
00599       field.setTypeName( typeName );
00600       remoteLayer->addAttribute( field );
00601     }
00602     else
00603     {
00604       showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
00605     }
00606 
00607     emit progressUpdated( i + 1 );
00608   }
00609 }
00610 
00611 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
00612 {
00613   QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
00614   QList<int> newFeatureIds = sqlQueryInts( db, sql );
00615 
00616   // get new features from offline layer
00617   QgsFeatureList features;
00618   for ( int i = 0; i < newFeatureIds.size(); i++ )
00619   {
00620     QgsFeature feature;
00621     if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
00622     {
00623       features << feature;
00624     }
00625   }
00626 
00627   // copy features to remote layer
00628   emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
00629 
00630   int i = 1;
00631   int newAttrsCount = remoteLayer->pendingFields().count();
00632   for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
00633   {
00634     QgsFeature f = *it;
00635 
00636     // NOTE: Spatialite provider ignores position of geometry column
00637     // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
00638     QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
00639     QgsAttributes newAttrs( newAttrsCount );
00640     QgsAttributes attrs = f.attributes();
00641     for ( int it = 0; it < attrs.count(); ++it )
00642     {
00643       newAttrs[ attrLookup[ it ] ] = attrs[ it ];
00644     }
00645     f.setAttributes( newAttrs );
00646 
00647     remoteLayer->addFeature( f, false );
00648 
00649     emit progressUpdated( i++ );
00650   }
00651 }
00652 
00653 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
00654 {
00655   QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
00656   QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
00657 
00658   emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
00659 
00660   int i = 1;
00661   for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
00662   {
00663     QgsFeatureId fid = remoteFid( db, layerId, *it );
00664     remoteLayer->deleteFeature( fid );
00665 
00666     emit progressUpdated( i++ );
00667   }
00668 }
00669 
00670 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
00671 {
00672   QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
00673   AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
00674 
00675   emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
00676 
00677   QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
00678 
00679   for ( int i = 0; i < values.size(); i++ )
00680   {
00681     QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
00682 
00683     remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value, false );
00684 
00685     emit progressUpdated( i + 1 );
00686   }
00687 }
00688 
00689 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
00690 {
00691   QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
00692   GeometryChanges values = sqlQueryGeometryChanges( db, sql );
00693 
00694   emit progressModeSet( QgsOfflineEditing::UpdateGeometries, values.size() );
00695 
00696   for ( int i = 0; i < values.size(); i++ )
00697   {
00698     QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
00699     remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) );
00700 
00701     emit progressUpdated( i + 1 );
00702   }
00703 }
00704 
00705 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
00706 {
00707   // update fid lookup for added features
00708 
00709   // get remote added fids
00710   // NOTE: use QMap for sorted fids
00711   QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
00712   QgsFeature f;
00713 
00714   QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
00715 
00716   emit progressModeSet( QgsOfflineEditing::ProcessFeatures, remoteLayer->featureCount() );
00717 
00718   int i = 1;
00719   while ( fit.nextFeature( f ) )
00720   {
00721     if ( offlineFid( db, layerId, f.id() ) == -1 )
00722     {
00723       newRemoteFids[ f.id()] = true;
00724     }
00725 
00726     emit progressUpdated( i++ );
00727   }
00728 
00729   // get local added fids
00730   // NOTE: fids are sorted
00731   QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
00732   QList<int> newOfflineFids = sqlQueryInts( db, sql );
00733 
00734   if ( newRemoteFids.size() != newOfflineFids.size() )
00735   {
00736     //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
00737   }
00738   else
00739   {
00740     // add new fid lookups
00741     i = 0;
00742     sqlExec( db, "BEGIN" );
00743     for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
00744     {
00745       addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
00746     }
00747     sqlExec( db, "COMMIT" );
00748   }
00749 }
00750 
00751 void QgsOfflineEditing::copySymbology( const QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
00752 {
00753   QString error;
00754   QDomDocument doc;
00755   QDomElement node = doc.createElement( "symbology" );
00756   doc.appendChild( node );
00757   sourceLayer->writeSymbology( node, doc, error );
00758 
00759   if ( error.isEmpty() )
00760   {
00761     targetLayer->readSymbology( node, error );
00762   }
00763   if ( !error.isEmpty() )
00764   {
00765     showWarning( error );
00766   }
00767 }
00768 
00769 // NOTE: use this to map column indices in case the remote geometry column is not last
00770 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
00771 {
00772   const QgsAttributeList& offlineAttrs = offlineLayer->pendingAllAttributesList();
00773   const QgsAttributeList& remoteAttrs = remoteLayer->pendingAllAttributesList();
00774 
00775   QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
00776   // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
00777   for ( int i = 0; i < remoteAttrs.size(); i++ )
00778   {
00779     attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
00780   }
00781 
00782   return attrLookup;
00783 }
00784 
00785 void QgsOfflineEditing::showWarning( const QString& message )
00786 {
00787   QMessageBox::warning( NULL, tr( "Offline Editing Plugin" ), message );
00788 }
00789 
00790 sqlite3* QgsOfflineEditing::openLoggingDb()
00791 {
00792   sqlite3* db = NULL;
00793   QString dbPath = QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
00794   if ( !dbPath.isEmpty() )
00795   {
00796     int rc = sqlite3_open( dbPath.toStdString().c_str(), &db );
00797     if ( rc != SQLITE_OK )
00798     {
00799       showWarning( tr( "Could not open the spatialite logging database" ) );
00800       sqlite3_close( db );
00801       db = NULL;
00802     }
00803   }
00804   return db;
00805 }
00806 
00807 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
00808 {
00809   QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
00810   int layerId = sqlQueryInt( db, sql, -1 );
00811   if ( layerId == -1 )
00812   {
00813     // next layer id
00814     sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
00815     int newLayerId = sqlQueryInt( db, sql, -1 );
00816 
00817     // insert layer
00818     sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
00819     sqlExec( db, sql );
00820 
00821     // increase layer_id
00822     // TODO: use trigger for auto increment?
00823     sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
00824     sqlExec( db, sql );
00825 
00826     layerId = newLayerId;
00827   }
00828 
00829   return layerId;
00830 }
00831 
00832 int QgsOfflineEditing::getCommitNo( sqlite3* db )
00833 {
00834   QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
00835   return sqlQueryInt( db, sql, -1 );
00836 }
00837 
00838 void QgsOfflineEditing::increaseCommitNo( sqlite3* db )
00839 {
00840   QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
00841   sqlExec( db, sql );
00842 }
00843 
00844 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
00845 {
00846   QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
00847   sqlExec( db, sql );
00848 }
00849 
00850 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, QgsFeatureId offlineFid )
00851 {
00852   QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
00853   return sqlQueryInt( db, sql, -1 );
00854 }
00855 
00856 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, QgsFeatureId remoteFid )
00857 {
00858   QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
00859   return sqlQueryInt( db, sql, -1 );
00860 }
00861 
00862 bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, QgsFeatureId fid )
00863 {
00864   QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
00865   return ( sqlQueryInt( db, sql, 0 ) > 0 );
00866 }
00867 
00868 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
00869 {
00870   char * errmsg;
00871   int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
00872   if ( rc != SQLITE_OK )
00873   {
00874     showWarning( errmsg );
00875   }
00876   return rc;
00877 }
00878 
00879 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
00880 {
00881   sqlite3_stmt* stmt = NULL;
00882   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
00883   {
00884     showWarning( sqlite3_errmsg( db ) );
00885     return defaultValue;
00886   }
00887 
00888   int value = defaultValue;
00889   int ret = sqlite3_step( stmt );
00890   if ( ret == SQLITE_ROW )
00891   {
00892     value = sqlite3_column_int( stmt, 0 );
00893   }
00894   sqlite3_finalize( stmt );
00895 
00896   return value;
00897 }
00898 
00899 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
00900 {
00901   QList<int> values;
00902 
00903   sqlite3_stmt* stmt = NULL;
00904   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
00905   {
00906     showWarning( sqlite3_errmsg( db ) );
00907     return values;
00908   }
00909 
00910   int ret = sqlite3_step( stmt );
00911   while ( ret == SQLITE_ROW )
00912   {
00913     values << sqlite3_column_int( stmt, 0 );
00914 
00915     ret = sqlite3_step( stmt );
00916   }
00917   sqlite3_finalize( stmt );
00918 
00919   return values;
00920 }
00921 
00922 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
00923 {
00924   QList<QgsField> values;
00925 
00926   sqlite3_stmt* stmt = NULL;
00927   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
00928   {
00929     showWarning( sqlite3_errmsg( db ) );
00930     return values;
00931   }
00932 
00933   int ret = sqlite3_step( stmt );
00934   while ( ret == SQLITE_ROW )
00935   {
00936     QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ),
00937                     ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
00938                     "", // typeName
00939                     sqlite3_column_int( stmt, 2 ),
00940                     sqlite3_column_int( stmt, 3 ),
00941                     QString(( const char* )sqlite3_column_text( stmt, 4 ) ) );
00942     values << field;
00943 
00944     ret = sqlite3_step( stmt );
00945   }
00946   sqlite3_finalize( stmt );
00947 
00948   return values;
00949 }
00950 
00951 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql )
00952 {
00953   QgsFeatureIds values;
00954 
00955   sqlite3_stmt* stmt = NULL;
00956   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
00957   {
00958     showWarning( sqlite3_errmsg( db ) );
00959     return values;
00960   }
00961 
00962   int ret = sqlite3_step( stmt );
00963   while ( ret == SQLITE_ROW )
00964   {
00965     values << sqlite3_column_int( stmt, 0 );
00966 
00967     ret = sqlite3_step( stmt );
00968   }
00969   sqlite3_finalize( stmt );
00970 
00971   return values;
00972 }
00973 
00974 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql )
00975 {
00976   AttributeValueChanges values;
00977 
00978   sqlite3_stmt* stmt = NULL;
00979   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
00980   {
00981     showWarning( sqlite3_errmsg( db ) );
00982     return values;
00983   }
00984 
00985   int ret = sqlite3_step( stmt );
00986   while ( ret == SQLITE_ROW )
00987   {
00988     AttributeValueChange change;
00989     change.fid = sqlite3_column_int( stmt, 0 );
00990     change.attr = sqlite3_column_int( stmt, 1 );
00991     change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) );
00992     values << change;
00993 
00994     ret = sqlite3_step( stmt );
00995   }
00996   sqlite3_finalize( stmt );
00997 
00998   return values;
00999 }
01000 
01001 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql )
01002 {
01003   GeometryChanges values;
01004 
01005   sqlite3_stmt* stmt = NULL;
01006   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
01007   {
01008     showWarning( sqlite3_errmsg( db ) );
01009     return values;
01010   }
01011 
01012   int ret = sqlite3_step( stmt );
01013   while ( ret == SQLITE_ROW )
01014   {
01015     GeometryChange change;
01016     change.fid = sqlite3_column_int( stmt, 0 );
01017     change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) );
01018     values << change;
01019 
01020     ret = sqlite3_step( stmt );
01021   }
01022   sqlite3_finalize( stmt );
01023 
01024   return values;
01025 }
01026 
01027 void QgsOfflineEditing::layerAdded( QgsMapLayer* layer )
01028 {
01029   // detect offline layer
01030   if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
01031   {
01032     // enable logging
01033     connect( layer, SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
01034              this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
01035     connect( layer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
01036              this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
01037     connect( layer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
01038              this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
01039     connect( layer, SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
01040              this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
01041     connect( layer, SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
01042              this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
01043   }
01044 }
01045 
01046 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
01047 {
01048   sqlite3* db = openLoggingDb();
01049   if ( db == NULL )
01050   {
01051     return;
01052   }
01053 
01054   // insert log
01055   int layerId = getOrCreateLayerId( db, qgisLayerId );
01056   int commitNo = getCommitNo( db );
01057 
01058   for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
01059   {
01060     QgsField field = *it;
01061     QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
01062                   .arg( layerId )
01063                   .arg( commitNo )
01064                   .arg( field.name() )
01065                   .arg( field.type() )
01066                   .arg( field.length() )
01067                   .arg( field.precision() )
01068                   .arg( field.comment() );
01069     sqlExec( db, sql );
01070   }
01071 
01072   increaseCommitNo( db );
01073   sqlite3_close( db );
01074 }
01075 
01076 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
01077 {
01078   sqlite3* db = openLoggingDb();
01079   if ( db == NULL )
01080   {
01081     return;
01082   }
01083 
01084   // insert log
01085   int layerId = getOrCreateLayerId( db, qgisLayerId );
01086 
01087   // get new feature ids from db
01088   QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
01089   QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
01090 
01091   // only store feature ids
01092   QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
01093   QList<int> newFeatureIds = sqlQueryInts( db, sql );
01094   for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
01095   {
01096     QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
01097                   .arg( layerId )
01098                   .arg( newFeatureIds.at( i ) );
01099     sqlExec( db, sql );
01100   }
01101 
01102   sqlite3_close( db );
01103 }
01104 
01105 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
01106 {
01107   sqlite3* db = openLoggingDb();
01108   if ( db == NULL )
01109   {
01110     return;
01111   }
01112 
01113   // insert log
01114   int layerId = getOrCreateLayerId( db, qgisLayerId );
01115 
01116   for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
01117   {
01118     if ( isAddedFeature( db, layerId, *it ) )
01119     {
01120       // remove from added features log
01121       QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
01122       sqlExec( db, sql );
01123     }
01124     else
01125     {
01126       QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
01127                     .arg( layerId )
01128                     .arg( *it );
01129       sqlExec( db, sql );
01130     }
01131   }
01132 
01133   sqlite3_close( db );
01134 }
01135 
01136 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
01137 {
01138   sqlite3* db = openLoggingDb();
01139   if ( db == NULL )
01140   {
01141     return;
01142   }
01143 
01144   // insert log
01145   int layerId = getOrCreateLayerId( db, qgisLayerId );
01146   int commitNo = getCommitNo( db );
01147 
01148   for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
01149   {
01150     QgsFeatureId fid = cit.key();
01151     if ( isAddedFeature( db, layerId, fid ) )
01152     {
01153       // skip added features
01154       continue;
01155     }
01156     QgsAttributeMap attrMap = cit.value();
01157     for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
01158     {
01159       QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
01160                     .arg( layerId )
01161                     .arg( commitNo )
01162                     .arg( fid )
01163                     .arg( it.key() ) // attr
01164                     .arg( it.value().toString() ); // value
01165       sqlExec( db, sql );
01166     }
01167   }
01168 
01169   increaseCommitNo( db );
01170   sqlite3_close( db );
01171 }
01172 
01173 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
01174 {
01175   sqlite3* db = openLoggingDb();
01176   if ( db == NULL )
01177   {
01178     return;
01179   }
01180 
01181   // insert log
01182   int layerId = getOrCreateLayerId( db, qgisLayerId );
01183   int commitNo = getCommitNo( db );
01184 
01185   for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
01186   {
01187     QgsFeatureId fid = it.key();
01188     if ( isAddedFeature( db, layerId, fid ) )
01189     {
01190       // skip added features
01191       continue;
01192     }
01193     QgsGeometry geom = it.value();
01194     QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
01195                   .arg( layerId )
01196                   .arg( commitNo )
01197                   .arg( fid )
01198                   .arg( geom.exportToWkt() );
01199     sqlExec( db, sql );
01200 
01201     // TODO: use WKB instead of WKT?
01202   }
01203 
01204   increaseCommitNo( db );
01205   sqlite3_close( db );
01206 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines