QGIS API Documentation  2.12.0-Lyon
qgsofflineediting.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  offline_editing.cpp
3 
4  Offline Editing Plugin
5  a QGIS plugin
6  --------------------------------------
7  Date : 22-Jul-2010
8  Copyright : (C) 2010 by Sourcepole
9  Email : info at sourcepole.ch
10  ***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 
20 #include "qgsapplication.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsgeometry.h"
23 #include "qgslayertreegroup.h"
24 #include "qgslayertreelayer.h"
25 #include "qgsmaplayer.h"
26 #include "qgsmaplayerregistry.h"
27 #include "qgsofflineediting.h"
28 #include "qgsproject.h"
29 #include "qgsvectordataprovider.h"
32 #include "qgsslconnect.h"
33 
34 #include <QDir>
35 #include <QDomDocument>
36 #include <QDomNode>
37 #include <QFile>
38 #include <QMessageBox>
39 
40 extern "C"
41 {
42 #include <sqlite3.h>
43 #include <spatialite.h>
44 }
45 
46 // TODO: DEBUG
47 #include <QDebug>
48 // END
49 
50 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
51 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
52 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
53 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
54 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
55 
57 {
58  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
59 }
60 
62 {
63 }
64 
69 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds )
70 {
71  if ( layerIds.isEmpty() )
72  {
73  return false;
74  }
75  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
76  if ( createSpatialiteDB( dbPath ) )
77  {
78  sqlite3* db;
79  int rc = QgsSLConnect::sqlite3_open( dbPath.toUtf8().constData(), &db );
80  if ( rc != SQLITE_OK )
81  {
82  showWarning( tr( "Could not open the spatialite database" ) );
83  }
84  else
85  {
86  // create logging tables
87  createLoggingTables( db );
88 
89  emit progressStarted();
90 
91  QMap<QString, QgsVectorJoinList > joinInfoBuffer;
92  QMap<QString, QgsVectorLayer*> layerIdMapping;
93 
94  for ( int i = 0; i < layerIds.count(); i++ )
95  {
96  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
97  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( layer );
98  QgsVectorJoinList joins = vl->vectorJoins();
99 
100  // Layer names will be appended an _offline suffix
101  // Join fields are prefixed with the layer name and we do not want the
102  // field name to change so we stabilize the field name by defining a
103  // custom prefix with the layername without _offline suffix.
104  QgsVectorJoinList::iterator it = joins.begin();
105  while ( it != joins.end() )
106  {
107  if (( *it ).prefix.isNull() )
108  {
109  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer(( *it ).joinLayerId ) );
110 
111  if ( vl )
112  ( *it ).prefix = vl->name() + "_";
113  }
114  ++it;
115  }
116  joinInfoBuffer.insert( vl->id(), joins );
117  }
118 
119  // copy selected vector layers to SpatiaLite
120  for ( int i = 0; i < layerIds.count(); i++ )
121  {
122  emit layerProgressUpdated( i + 1, layerIds.count() );
123 
124  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
125  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( layer );
126  QString origLayerId = vl->id();
127  QgsVectorLayer* newLayer = copyVectorLayer( vl, db, dbPath );
128 
129  if ( newLayer )
130  {
131  layerIdMapping.insert( origLayerId, newLayer );
132  }
133  }
134 
135  // restore join info on new spatialite layer
137  for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
138  {
139  QgsVectorLayer* newLayer = layerIdMapping.value( it.key() );
140 
141  if ( newLayer )
142  {
143  Q_FOREACH ( QgsVectorJoinInfo join, it.value() )
144  {
145  QgsVectorLayer* newJoinedLayer = layerIdMapping.value( join.joinLayerId );
146  if ( newJoinedLayer )
147  {
148  // If the layer has been offline'd, update join information
149  join.joinLayerId = newJoinedLayer->id();
150  }
151  newLayer->addJoin( join );
152  }
153  }
154  }
155 
156 
157  emit progressStopped();
158 
160 
161  // save offline project
162  QString projectTitle = QgsProject::instance()->title();
163  if ( projectTitle.isEmpty() )
164  {
165  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
166  }
167  projectTitle += " (offline)";
168  QgsProject::instance()->setTitle( projectTitle );
169 
171 
172  return true;
173  }
174  }
175 
176  return false;
177 
178  // Workflow:
179  // copy layers to spatialite
180  // create spatialite db at offlineDataPath
181  // create table for each layer
182  // add new spatialite layer
183  // copy features
184  // save as offline project
185  // mark offline layers
186  // remove remote layers
187  // mark as offline project
188 }
189 
191 {
193 }
194 
196 {
197  // open logging db
198  sqlite3* db = openLoggingDb();
199  if ( db == NULL )
200  {
201  return;
202  }
203 
204  emit progressStarted();
205 
206  // restore and sync remote layers
207  QList<QgsMapLayer*> offlineLayers;
209  for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
210  {
211  QgsMapLayer* layer = layer_it.value();
213  {
214  offlineLayers << layer;
215  }
216  }
217 
218  for ( int l = 0; l < offlineLayers.count(); l++ )
219  {
220  QgsMapLayer* layer = offlineLayers[l];
221 
222  emit layerProgressUpdated( l + 1, offlineLayers.count() );
223 
224  QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
225  QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
226  QString remoteName = layer->name();
227  remoteName.remove( QRegExp( " \\(offline\\)$" ) );
228 
229  QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
230  if ( remoteLayer->isValid() )
231  {
232  // TODO: only add remote layer if there are log entries?
233 
234  QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );
235 
236  // register this layer with the central layers registry
238  QList<QgsMapLayer *>() << remoteLayer, true );
239 
240  // copy style
241  copySymbology( offlineLayer, remoteLayer );
242 
243  // apply layer edit log
244  QString qgisLayerId = layer->id();
245  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
246  int layerId = sqlQueryInt( db, sql, -1 );
247  if ( layerId != -1 )
248  {
249  remoteLayer->startEditing();
250 
251  // TODO: only get commitNos of this layer?
252  int commitNo = getCommitNo( db );
253  for ( int i = 0; i < commitNo; i++ )
254  {
255  // apply commits chronologically
256  applyAttributesAdded( remoteLayer, db, layerId, i );
257  applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
258  applyGeometryChanges( remoteLayer, db, layerId, i );
259  }
260 
261  applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
262  applyFeaturesRemoved( remoteLayer, db, layerId );
263 
264  if ( remoteLayer->commitChanges() )
265  {
266  // update fid lookup
267  updateFidLookup( remoteLayer, db, layerId );
268 
269  // clear edit log for this layer
270  sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
271  sqlExec( db, sql );
272  sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
273  sqlExec( db, sql );
274  sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
275  sqlExec( db, sql );
276  sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
277  sqlExec( db, sql );
278  sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
279  sqlExec( db, sql );
280 
281  // reset commitNo
282  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
283  sqlExec( db, sql );
284  }
285  else
286  {
287  showWarning( remoteLayer->commitErrors().join( "\n" ) );
288  }
289  }
290 
291  // remove offline layer
293  ( QStringList() << qgisLayerId ) );
294 
295  // disable offline project
296  QString projectTitle = QgsProject::instance()->title();
297  projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
298  QgsProject::instance()->setTitle( projectTitle );
300  remoteLayer->reload(); //update with other changes
301  }
302  }
303 
304  emit progressStopped();
305 
306  sqlite3_close( db );
307 }
308 
309 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
310 {
311  // attempting to perform self-initialization for a newly created DB
312  if ( !sqlite_handle )
313  return;
314  // checking if this DB is really empty
315  char **results;
316  int rows, columns;
317  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, NULL );
318  if ( ret != SQLITE_OK )
319  return;
320  int count = 0;
321  if ( rows >= 1 )
322  {
323  for ( int i = 1; i <= rows; i++ )
324  count = atoi( results[( i * columns ) + 0] );
325  }
326 
327  sqlite3_free_table( results );
328 
329  if ( count > 0 )
330  return;
331 
332  bool above41 = false;
333  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, NULL );
334  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
335  {
336  QString version = QString::fromUtf8( results[1] );
337  QStringList parts = version.split( " ", QString::SkipEmptyParts );
338  if ( parts.size() >= 1 )
339  {
340  QStringList verparts = parts[0].split( ".", QString::SkipEmptyParts );
341  above41 = verparts.size() >= 2 && ( verparts[0].toInt() > 4 || ( verparts[0].toInt() == 4 && verparts[1].toInt() >= 1 ) );
342  }
343  }
344 
345  sqlite3_free_table( results );
346 
347  // all right, it's empty: proceding to initialize
348  char *errMsg = 0;
349  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", NULL, NULL, &errMsg );
350 
351  if ( ret != SQLITE_OK )
352  {
353  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
354  errCause += QString::fromUtf8( errMsg );
355  showWarning( errCause );
356  sqlite3_free( errMsg );
357  return;
358  }
359  spatial_ref_sys_init( sqlite_handle, 0 );
360 }
361 
362 bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath )
363 {
364  int ret;
365  sqlite3 *sqlite_handle;
366  char *errMsg = NULL;
367  QFile newDb( offlineDbPath );
368  if ( newDb.exists() )
369  {
370  QFile::remove( offlineDbPath );
371  }
372 
373  // see also QgsNewSpatialiteLayerDialog::createDb()
374 
375  QFileInfo fullPath = QFileInfo( offlineDbPath );
376  QDir path = fullPath.dir();
377 
378  // Must be sure there is destination directory ~/.qgis
379  QDir().mkpath( path.absolutePath() );
380 
381  // creating/opening the new database
382  QString dbPath = newDb.fileName();
383  ret = QgsSLConnect::sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
384  if ( ret )
385  {
386  // an error occurred
387  QString errCause = tr( "Could not create a new database\n" );
388  errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
389  sqlite3_close( sqlite_handle );
390  showWarning( errCause );
391  return false;
392  }
393  // activating Foreign Key constraints
394  ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
395  if ( ret != SQLITE_OK )
396  {
397  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
398  sqlite3_free( errMsg );
399  QgsSLConnect::sqlite3_close( sqlite_handle );
400  return false;
401  }
402  initializeSpatialMetadata( sqlite_handle );
403 
404  // all done: closing the DB connection
405  QgsSLConnect::sqlite3_close( sqlite_handle );
406 
407  return true;
408 }
409 
410 void QgsOfflineEditing::createLoggingTables( sqlite3* db )
411 {
412  // indices
413  QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
414  sqlExec( db, sql );
415 
416  sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
417  sqlExec( db, sql );
418 
419  sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
420  sqlExec( db, sql );
421 
422  // layername <-> layer id
423  sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
424  sqlExec( db, sql );
425 
426  // offline fid <-> remote fid
427  sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
428  sqlExec( db, sql );
429 
430  // added attributes
431  sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
432  sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
433  sqlExec( db, sql );
434 
435  // added features
436  sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
437  sqlExec( db, sql );
438 
439  // removed features
440  sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
441  sqlExec( db, sql );
442 
443  // feature updates
444  sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
445  sqlExec( db, sql );
446 
447  // geometry updates
448  sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
449  sqlExec( db, sql );
450 
451  /* TODO: other logging tables
452  - attr delete (not supported by SpatiaLite provider)
453  */
454 }
455 
456 QgsVectorLayer* QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
457 {
458  if ( layer == 0 )
459  {
460  return 0;
461  }
462 
463  QString tableName = layer->id();
464 
465  // create table
466  QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
467  QString delim = "";
468  const QgsFields& fields = layer->dataProvider()->fields();
469  for ( int idx = 0; idx < fields.count(); ++idx )
470  {
471  QString dataType = "";
472  QVariant::Type type = fields[idx].type();
473  if ( type == QVariant::Int || type == QVariant::LongLong )
474  {
475  dataType = "INTEGER";
476  }
477  else if ( type == QVariant::Double )
478  {
479  dataType = "REAL";
480  }
481  else if ( type == QVariant::String )
482  {
483  dataType = "TEXT";
484  }
485  else
486  {
487  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( fields[idx].name(), QVariant::typeToName( type ) ) );
488  }
489 
490  sql += delim + QString( "'%1' %2" ).arg( fields[idx].name(), dataType );
491  delim = ",";
492  }
493  sql += ")";
494 
495  int rc = sqlExec( db, sql );
496 
497  // add geometry column
498  if ( layer->hasGeometryType() )
499  {
500  QString geomType = "";
501  switch ( layer->wkbType() )
502  {
503  case QGis::WKBPoint:
504  geomType = "POINT";
505  break;
506  case QGis::WKBMultiPoint:
507  geomType = "MULTIPOINT";
508  break;
509  case QGis::WKBLineString:
510  geomType = "LINESTRING";
511  break;
513  geomType = "MULTILINESTRING";
514  break;
515  case QGis::WKBPolygon:
516  geomType = "POLYGON";
517  break;
519  geomType = "MULTIPOLYGON";
520  break;
521  default:
522  showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
523  break;
524  };
525  QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
526  .arg( tableName )
527  .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
528  .arg( geomType );
529 
530  // create spatial index
531  QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
532 
533  if ( rc == SQLITE_OK )
534  {
535  rc = sqlExec( db, sqlAddGeom );
536  if ( rc == SQLITE_OK )
537  {
538  rc = sqlExec( db, sqlCreateIndex );
539  }
540  }
541  }
542 
543  if ( rc == SQLITE_OK )
544  {
545  // add new layer
546  QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'%3 sql=" )
547  .arg( offlineDbPath,
548  tableName, layer->hasGeometryType() ? "(Geometry)" : "" ),
549  layer->name() + " (offline)", "spatialite" );
550  if ( newLayer->isValid() )
551  {
552  // mark as offline layer
554 
555  // store original layer source
558 
559  // register this layer with the central layers registry
561  QList<QgsMapLayer *>() << newLayer );
562 
563  // copy style
565  bool hasLabels = layer->hasLabelsEnabled();
567  if ( !hasLabels )
568  {
569  // NOTE: copy symbology before adding the layer so it is displayed correctly
570  copySymbology( layer, newLayer );
571  }
572 
574  // Find the parent group of the original layer
575  QgsLayerTreeLayer* layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
576  if ( layerTreeLayer )
577  {
578  QgsLayerTreeGroup* parentTreeGroup = qobject_cast<QgsLayerTreeGroup*>( layerTreeLayer->parent() );
579  if ( parentTreeGroup )
580  {
581  int index = parentTreeGroup->children().indexOf( layerTreeLayer );
582  // Move the new layer from the root group to the new group
583  QgsLayerTreeLayer* newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
584  if ( newLayerTreeLayer )
585  {
586  QgsLayerTreeNode* newLayerTreeLayerClone = newLayerTreeLayer->clone();
587  QgsLayerTreeGroup* grp = qobject_cast<QgsLayerTreeGroup*>( newLayerTreeLayer->parent() );
588  parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
589  if ( grp )
590  grp->removeChildNode( newLayerTreeLayer );
591  }
592  }
593  }
594 
595  if ( hasLabels )
596  {
597  // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
598  copySymbology( layer, newLayer );
599  }
600 
601  // copy features
602  newLayer->startEditing();
603  QgsFeature f;
604 
605  // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
606  layer->setSubsetString( layer->subsetString() );
607 
608  QgsFeatureIterator fit = layer->dataProvider()->getFeatures();
609 
611  int featureCount = 1;
612 
613  QList<QgsFeatureId> remoteFeatureIds;
614  while ( fit.nextFeature( f ) )
615  {
616  remoteFeatureIds << f.id();
617 
618  // NOTE: Spatialite provider ignores position of geometry column
619  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
620  int column = 0;
621  QgsAttributes attrs = f.attributes();
622  QgsAttributes newAttrs( attrs.count() );
623  for ( int it = 0; it < attrs.count(); ++it )
624  {
625  newAttrs[column++] = attrs.at( it );
626  }
627  f.setAttributes( newAttrs );
628 
629  newLayer->addFeature( f, false );
630 
631  emit progressUpdated( featureCount++ );
632  }
633  if ( newLayer->commitChanges() )
634  {
636  featureCount = 1;
637 
638  // update feature id lookup
639  int layerId = getOrCreateLayerId( db, newLayer->id() );
640  QList<QgsFeatureId> offlineFeatureIds;
641 
642  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
643  while ( fit.nextFeature( f ) )
644  {
645  offlineFeatureIds << f.id();
646  }
647 
648  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
649  sqlExec( db, "BEGIN" );
650  int remoteCount = remoteFeatureIds.size();
651  for ( int i = 0; i < remoteCount; i++ )
652  {
653  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
654  emit progressUpdated( featureCount++ );
655  }
656  sqlExec( db, "COMMIT" );
657  }
658  else
659  {
660  showWarning( newLayer->commitErrors().join( "\n" ) );
661  }
662 
663  // remove remote layer
665  QStringList() << layer->id() );
666  }
667  return newLayer;
668  }
669  return 0;
670 }
671 
672 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
673 {
674  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 );
675  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
676 
677  const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
678  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
679 
680  // NOTE: uses last matching QVariant::Type of nativeTypes
681  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
682  for ( int i = 0; i < nativeTypes.size(); i++ )
683  {
684  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
685  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
686  }
687 
689 
690  for ( int i = 0; i < fields.size(); i++ )
691  {
692  // lookup typename from layer provider
693  QgsField field = fields[i];
694  if ( typeNameLookup.contains( field.type() ) )
695  {
696  QString typeName = typeNameLookup[ field.type()];
697  field.setTypeName( typeName );
698  remoteLayer->addAttribute( field );
699  }
700  else
701  {
702  showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
703  }
704 
705  emit progressUpdated( i + 1 );
706  }
707 }
708 
709 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
710 {
711  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
712  QList<int> newFeatureIds = sqlQueryInts( db, sql );
713 
714  // get default value for each field
715  const QgsFields& remoteFlds = remoteLayer->fields();
716  QVector<QVariant> defaultValues( remoteFlds.count() );
717  for ( int i = 0; i < remoteFlds.count(); ++i )
718  {
719  if ( remoteFlds.fieldOrigin( i ) == QgsFields::OriginProvider )
720  defaultValues[i] = remoteLayer->dataProvider()->defaultValue( remoteFlds.fieldOriginIndex( i ) );
721  }
722 
723  // get new features from offline layer
724  QgsFeatureList features;
725  for ( int i = 0; i < newFeatureIds.size(); i++ )
726  {
727  QgsFeature feature;
728  if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
729  {
730  features << feature;
731  }
732  }
733 
734  // copy features to remote layer
736 
737  int i = 1;
738  int newAttrsCount = remoteLayer->fields().count();
739  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
740  {
741  QgsFeature f = *it;
742 
743  // NOTE: Spatialite provider ignores position of geometry column
744  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
745  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
746  QgsAttributes newAttrs( newAttrsCount );
747  QgsAttributes attrs = f.attributes();
748  for ( int it = 0; it < attrs.count(); ++it )
749  {
750  newAttrs[ attrLookup[ it ] ] = attrs.at( it );
751  }
752 
753  // try to use default value from the provider
754  // (important especially e.g. for postgis primary key generated from a sequence)
755  for ( int k = 0; k < newAttrs.count(); ++k )
756  {
757  if ( newAttrs.at( k ).isNull() && !defaultValues.at( k ).isNull() )
758  newAttrs[k] = defaultValues.at( k );
759  }
760 
761  f.setAttributes( newAttrs );
762 
763  remoteLayer->addFeature( f, false );
764 
765  emit progressUpdated( i++ );
766  }
767 }
768 
769 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
770 {
771  QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
772  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
773 
775 
776  int i = 1;
777  for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
778  {
779  QgsFeatureId fid = remoteFid( db, layerId, *it );
780  remoteLayer->deleteFeature( fid );
781 
782  emit progressUpdated( i++ );
783  }
784 }
785 
786 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
787 {
788  QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
789  AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
790 
791  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
792 
793  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
794 
795  for ( int i = 0; i < values.size(); i++ )
796  {
797  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
798 
799  remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
800 
801  emit progressUpdated( i + 1 );
802  }
803 }
804 
805 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
806 {
807  QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
808  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
809 
811 
812  for ( int i = 0; i < values.size(); i++ )
813  {
814  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
815  remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) );
816 
817  emit progressUpdated( i + 1 );
818  }
819 }
820 
821 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
822 {
823  // update fid lookup for added features
824 
825  // get remote added fids
826  // NOTE: use QMap for sorted fids
827  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
828  QgsFeature f;
829 
830  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
831 
833 
834  int i = 1;
835  while ( fit.nextFeature( f ) )
836  {
837  if ( offlineFid( db, layerId, f.id() ) == -1 )
838  {
839  newRemoteFids[ f.id()] = true;
840  }
841 
842  emit progressUpdated( i++ );
843  }
844 
845  // get local added fids
846  // NOTE: fids are sorted
847  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
848  QList<int> newOfflineFids = sqlQueryInts( db, sql );
849 
850  if ( newRemoteFids.size() != newOfflineFids.size() )
851  {
852  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
853  }
854  else
855  {
856  // add new fid lookups
857  i = 0;
858  sqlExec( db, "BEGIN" );
859  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
860  {
861  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
862  }
863  sqlExec( db, "COMMIT" );
864  }
865 }
866 
867 void QgsOfflineEditing::copySymbology( QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
868 {
869  QString error;
870  QDomDocument doc;
871  sourceLayer->exportNamedStyle( doc, error );
872 
873  if ( error.isEmpty() )
874  {
875  targetLayer->importNamedStyle( doc, error );
876  }
877  if ( !error.isEmpty() )
878  {
879  showWarning( error );
880  }
881 }
882 
883 // NOTE: use this to map column indices in case the remote geometry column is not last
884 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
885 {
886  const QgsAttributeList& offlineAttrs = offlineLayer->attributeList();
887  const QgsAttributeList& remoteAttrs = remoteLayer->attributeList();
888 
889  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
890  // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
891  for ( int i = 0; i < remoteAttrs.size(); i++ )
892  {
893  attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
894  }
895 
896  return attrLookup;
897 }
898 
899 void QgsOfflineEditing::showWarning( const QString& message )
900 {
901  emit warning( tr( "Offline Editing Plugin" ), message );
902 }
903 
904 sqlite3* QgsOfflineEditing::openLoggingDb()
905 {
906  sqlite3* db = NULL;
908  if ( !dbPath.isEmpty() )
909  {
910  int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
911  if ( rc != SQLITE_OK )
912  {
913  showWarning( tr( "Could not open the spatialite logging database" ) );
914  sqlite3_close( db );
915  db = NULL;
916  }
917  }
918  return db;
919 }
920 
921 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
922 {
923  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
924  int layerId = sqlQueryInt( db, sql, -1 );
925  if ( layerId == -1 )
926  {
927  // next layer id
928  sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
929  int newLayerId = sqlQueryInt( db, sql, -1 );
930 
931  // insert layer
932  sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
933  sqlExec( db, sql );
934 
935  // increase layer_id
936  // TODO: use trigger for auto increment?
937  sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
938  sqlExec( db, sql );
939 
940  layerId = newLayerId;
941  }
942 
943  return layerId;
944 }
945 
946 int QgsOfflineEditing::getCommitNo( sqlite3* db )
947 {
948  QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
949  return sqlQueryInt( db, sql, -1 );
950 }
951 
952 void QgsOfflineEditing::increaseCommitNo( sqlite3* db )
953 {
954  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
955  sqlExec( db, sql );
956 }
957 
958 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
959 {
960  QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
961  sqlExec( db, sql );
962 }
963 
964 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, QgsFeatureId offlineFid )
965 {
966  QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
967  return sqlQueryInt( db, sql, -1 );
968 }
969 
970 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, QgsFeatureId remoteFid )
971 {
972  QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
973  return sqlQueryInt( db, sql, -1 );
974 }
975 
976 bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, QgsFeatureId fid )
977 {
978  QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
979  return ( sqlQueryInt( db, sql, 0 ) > 0 );
980 }
981 
982 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
983 {
984  char * errmsg;
985  int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
986  if ( rc != SQLITE_OK )
987  {
988  showWarning( errmsg );
989  }
990  return rc;
991 }
992 
993 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
994 {
995  sqlite3_stmt* stmt = NULL;
996  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
997  {
998  showWarning( sqlite3_errmsg( db ) );
999  return defaultValue;
1000  }
1001 
1002  int value = defaultValue;
1003  int ret = sqlite3_step( stmt );
1004  if ( ret == SQLITE_ROW )
1005  {
1006  value = sqlite3_column_int( stmt, 0 );
1007  }
1008  sqlite3_finalize( stmt );
1009 
1010  return value;
1011 }
1012 
1013 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
1014 {
1015  QList<int> values;
1016 
1017  sqlite3_stmt* stmt = NULL;
1018  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1019  {
1020  showWarning( sqlite3_errmsg( db ) );
1021  return values;
1022  }
1023 
1024  int ret = sqlite3_step( stmt );
1025  while ( ret == SQLITE_ROW )
1026  {
1027  values << sqlite3_column_int( stmt, 0 );
1028 
1029  ret = sqlite3_step( stmt );
1030  }
1031  sqlite3_finalize( stmt );
1032 
1033  return values;
1034 }
1035 
1036 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
1037 {
1038  QList<QgsField> values;
1039 
1040  sqlite3_stmt* stmt = NULL;
1041  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1042  {
1043  showWarning( sqlite3_errmsg( db ) );
1044  return values;
1045  }
1046 
1047  int ret = sqlite3_step( stmt );
1048  while ( ret == SQLITE_ROW )
1049  {
1050  QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ),
1051  ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
1052  "", // typeName
1053  sqlite3_column_int( stmt, 2 ),
1054  sqlite3_column_int( stmt, 3 ),
1055  QString(( const char* )sqlite3_column_text( stmt, 4 ) ) );
1056  values << field;
1057 
1058  ret = sqlite3_step( stmt );
1059  }
1060  sqlite3_finalize( stmt );
1061 
1062  return values;
1063 }
1064 
1065 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql )
1066 {
1067  QgsFeatureIds values;
1068 
1069  sqlite3_stmt* stmt = NULL;
1070  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1071  {
1072  showWarning( sqlite3_errmsg( db ) );
1073  return values;
1074  }
1075 
1076  int ret = sqlite3_step( stmt );
1077  while ( ret == SQLITE_ROW )
1078  {
1079  values << sqlite3_column_int( stmt, 0 );
1080 
1081  ret = sqlite3_step( stmt );
1082  }
1083  sqlite3_finalize( stmt );
1084 
1085  return values;
1086 }
1087 
1088 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql )
1089 {
1090  AttributeValueChanges values;
1091 
1092  sqlite3_stmt* stmt = NULL;
1093  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1094  {
1095  showWarning( sqlite3_errmsg( db ) );
1096  return values;
1097  }
1098 
1099  int ret = sqlite3_step( stmt );
1100  while ( ret == SQLITE_ROW )
1101  {
1102  AttributeValueChange change;
1103  change.fid = sqlite3_column_int( stmt, 0 );
1104  change.attr = sqlite3_column_int( stmt, 1 );
1105  change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) );
1106  values << change;
1107 
1108  ret = sqlite3_step( stmt );
1109  }
1110  sqlite3_finalize( stmt );
1111 
1112  return values;
1113 }
1114 
1115 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql )
1116 {
1117  GeometryChanges values;
1118 
1119  sqlite3_stmt* stmt = NULL;
1120  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1121  {
1122  showWarning( sqlite3_errmsg( db ) );
1123  return values;
1124  }
1125 
1126  int ret = sqlite3_step( stmt );
1127  while ( ret == SQLITE_ROW )
1128  {
1129  GeometryChange change;
1130  change.fid = sqlite3_column_int( stmt, 0 );
1131  change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) );
1132  values << change;
1133 
1134  ret = sqlite3_step( stmt );
1135  }
1136  sqlite3_finalize( stmt );
1137 
1138  return values;
1139 }
1140 
1141 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
1142 {
1143  sqlite3* db = openLoggingDb();
1144  if ( db == NULL )
1145  {
1146  return;
1147  }
1148 
1149  // insert log
1150  int layerId = getOrCreateLayerId( db, qgisLayerId );
1151  int commitNo = getCommitNo( db );
1152 
1153  for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1154  {
1155  QgsField field = *it;
1156  QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1157  .arg( layerId )
1158  .arg( commitNo )
1159  .arg( field.name() )
1160  .arg( field.type() )
1161  .arg( field.length() )
1162  .arg( field.precision() )
1163  .arg( field.comment() );
1164  sqlExec( db, sql );
1165  }
1166 
1167  increaseCommitNo( db );
1168  sqlite3_close( db );
1169 }
1170 
1171 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
1172 {
1173  sqlite3* db = openLoggingDb();
1174  if ( db == NULL )
1175  {
1176  return;
1177  }
1178 
1179  // insert log
1180  int layerId = getOrCreateLayerId( db, qgisLayerId );
1181 
1182  // get new feature ids from db
1183  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
1184  QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
1185 
1186  // only store feature ids
1187  QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
1188  QList<int> newFeatureIds = sqlQueryInts( db, sql );
1189  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1190  {
1191  QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1192  .arg( layerId )
1193  .arg( newFeatureIds.at( i ) );
1194  sqlExec( db, sql );
1195  }
1196 
1197  sqlite3_close( db );
1198 }
1199 
1200 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
1201 {
1202  sqlite3* db = openLoggingDb();
1203  if ( db == NULL )
1204  {
1205  return;
1206  }
1207 
1208  // insert log
1209  int layerId = getOrCreateLayerId( db, qgisLayerId );
1210 
1211  for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1212  {
1213  if ( isAddedFeature( db, layerId, *it ) )
1214  {
1215  // remove from added features log
1216  QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1217  sqlExec( db, sql );
1218  }
1219  else
1220  {
1221  QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1222  .arg( layerId )
1223  .arg( *it );
1224  sqlExec( db, sql );
1225  }
1226  }
1227 
1228  sqlite3_close( db );
1229 }
1230 
1231 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
1232 {
1233  sqlite3* db = openLoggingDb();
1234  if ( db == NULL )
1235  {
1236  return;
1237  }
1238 
1239  // insert log
1240  int layerId = getOrCreateLayerId( db, qgisLayerId );
1241  int commitNo = getCommitNo( db );
1242 
1243  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1244  {
1245  QgsFeatureId fid = cit.key();
1246  if ( isAddedFeature( db, layerId, fid ) )
1247  {
1248  // skip added features
1249  continue;
1250  }
1251  QgsAttributeMap attrMap = cit.value();
1252  for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1253  {
1254  QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1255  .arg( layerId )
1256  .arg( commitNo )
1257  .arg( fid )
1258  .arg( it.key() ) // attr
1259  .arg( it.value().toString() ); // value
1260  sqlExec( db, sql );
1261  }
1262  }
1263 
1264  increaseCommitNo( db );
1265  sqlite3_close( db );
1266 }
1267 
1268 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
1269 {
1270  sqlite3* db = openLoggingDb();
1271  if ( db == NULL )
1272  {
1273  return;
1274  }
1275 
1276  // insert log
1277  int layerId = getOrCreateLayerId( db, qgisLayerId );
1278  int commitNo = getCommitNo( db );
1279 
1280  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1281  {
1282  QgsFeatureId fid = it.key();
1283  if ( isAddedFeature( db, layerId, fid ) )
1284  {
1285  // skip added features
1286  continue;
1287  }
1288  QgsGeometry geom = it.value();
1289  QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1290  .arg( layerId )
1291  .arg( commitNo )
1292  .arg( fid )
1293  .arg( geom.exportToWkt() );
1294  sqlExec( db, sql );
1295 
1296  // TODO: use WKB instead of WKT?
1297  }
1298 
1299  increaseCommitNo( db );
1300  sqlite3_close( db );
1301 }
1302 
1303 void QgsOfflineEditing::startListenFeatureChanges()
1304 {
1305  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1306  // enable logging
1307  connect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1308  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1309  connect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1310  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1311  connect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1312  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1313  connect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1314  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1315  connect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1316  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1317 }
1318 
1319 void QgsOfflineEditing::stopListenFeatureChanges()
1320 {
1321  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1322  // disable logging
1323  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1324  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1325  disconnect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1326  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1327  disconnect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1328  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1329  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1330  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1331  disconnect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1332  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1333 }
1334 
1335 void QgsOfflineEditing::layerAdded( QgsMapLayer* layer )
1336 {
1337  // detect offline layer
1339  {
1340  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( layer );
1341  connect( vLayer, SIGNAL( editingStarted() ), this, SLOT( startListenFeatureChanges() ) );
1342  connect( vLayer, SIGNAL( editingStopped() ), this, SLOT( stopListenFeatureChanges() ) );
1343  }
1344 }
1345 
1346 
virtual QgsLayerTreeNode * clone() const override
Create a copy of the node. Returns new instance.
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:53
virtual QString subsetString()
Get the string (typically sql) used to define a subset of the layer.
Layer tree group node serves as a container for layers and further groups.
const QString & name() const
Gets the name of the field.
Definition: qgsfield.cpp:72
Wrapper for iterator of features from vector data provider or vector layer.
void layerProgressUpdated(int layer, int numLayers)
Emit a signal that the next layer of numLayers has started processing.
static unsigned index
bool addJoin(const QgsVectorJoinInfo &joinInfo)
Joins another vector layer to this layer.
Base class for all map layer types.
Definition: qgsmaplayer.h:49
const QList< QgsVectorJoinInfo > vectorJoins() const
QgsAttributeList attributeList() const
Returns list of attribute indexes.
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds)
Convert current project for offline editing.
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
bool remove()
bool deleteFeature(QgsFeatureId fid)
Delete a feature from the layer (but does not commit it)
QgsFields fields() const
Returns the list of fields of this layer.
int size() const
QObject * sender() const
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
bool commitChanges()
Attempts to commit any changes to disk.
bool startEditing()
Make layer editable.
const_iterator constBegin() const
const T & at(int i) const
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:390
#define CUSTOM_PROPERTY_REMOTE_SOURCE
int precision() const
Gets the precision of the field.
Definition: qgsfield.cpp:92
Container of fields for a vector layer.
Definition: qgsfield.h:177
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:97
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group. The node will be deleted.
void removeMapLayers(const QStringList &theLayerIds)
Remove a set of layers from the registry.
bool addFeature(QgsFeature &f, bool alsoUpdateExtent=true)
Adds a feature.
field comes from the underlying data provider of the vector layer (originIndex = index in provider's ...
Definition: qgsfield.h:184
QString join(const QString &separator) const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:176
QString & remove(int position, int n)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString tr(const char *sourceText, const char *disambiguation, int n)
static int sqlite3_close(sqlite3 *)
int size() const
QgsMapLayer * mapLayer(const QString &theLayerId)
Retrieve a pointer to a loaded layer by id.
QGis::WkbType wkbType() const
Returns the WKBType or WKBUnknown in case of error.
int indexOf(const T &value, int from) const
bool isOfflineProject()
Return true if current project is offline.
long featureCount(QgsSymbolV2 *symbol)
Number of features rendered with specified symbol.
const QString & name() const
Get the display name of the layer.
static int sqlite3_open(const char *filename, sqlite3 **ppDb)
const char * name() const
bool writeEntry(const QString &scope, const QString &key, bool value)
void progressUpdated(int progress)
Emit a signal with the progress of the current mode.
QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on...
int count(const T &value) const
QString fromUtf8(const char *str, int size)
void progressStopped()
Emit a signal that processing of all layers has finished.
const QString & source() const
Returns the source for the layer.
QString fileName() const
QgsAttributes attributes() const
Returns the feature's attributes.
Definition: qgsfeature.cpp:92
Q_DECL_DEPRECATED void title(const QString &title)
Every project has an associated title string.
Definition: qgsproject.h:89
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:112
static int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs)
bool isEmpty() const
QgsLayerTreeNode * parent()
Get pointer to the parent. If parent is a null pointer, the node is a root node.
bool isEmpty() const
const_iterator constEnd() const
int fieldOriginIndex(int fieldIdx) const
Get field's origin index (its meaning is specific to each type of origin)
Definition: qgsfield.cpp:359
const char * constData() const
const QList< NativeType > & nativeTypes() const
Returns the names of the supported types.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
virtual long featureCount() const =0
Number of features in the layer.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
This class is a base class for nodes in a layer tree.
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())=0
Query the provider for features specified in request.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key.
int count() const
Return number of items.
Definition: qgsfield.cpp:311
bool changeGeometry(QgsFeatureId fid, QgsGeometry *geom)
Change feature's geometry.
QDir dir() const
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:40
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg)
Import the properties of this layer from a QDomDocument.
iterator end()
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
bool isValid()
Return the status of the layer.
#define PROJECT_ENTRY_SCOPE_OFFLINE
iterator begin()
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &value, bool emitSignal)
Changes an attribute value (but does not commit it)
const QStringList & commitErrors()
iterator end()
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
struct sqlite3 sqlite3
iterator begin()
virtual void reload() override
Synchronises with changes in the datasource.
const char * typeToName(Type typ)
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:391
const Key key(const T &value) const
long toLong(bool *ok, int base) const
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg)
Export the properties of this layer as named style in a QDomDocument.
QString providerType() const
Return the provider type for this layer.
const T & at(int i) const
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
virtual const QgsFields & fields() const =0
Return a map of indexes with field names for this layer.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=0) const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QString mid(int position, int n) const
virtual QVariant defaultValue(int fieldId)
Returns the default value for field specified by fieldId.
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position. The node must not have a parent yet. The node will be own...
QString absolutePath() const
void synchronize()
Synchronize to remote layers.
iterator end()
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:353
QString table() const
int length() const
Gets the length of the field.
Definition: qgsfield.cpp:87
int count(const T &value) const
void setTitle(const QString &title)
Set project title.
Definition: qgsproject.cpp:362
Q_DECL_DEPRECATED bool hasLabelsEnabled() const
Label is on.
QString absoluteFilePath(const QString &fileName) const
FieldOrigin fieldOrigin(int fieldIdx) const
Get field's origin (value from an enumeration)
Definition: qgsfield.cpp:351
QString authid() const
Get the authority identifier for this srs.
const QMap< QString, QgsMapLayer * > & mapLayers()
Retrieve the mapLayers collection (mainly intended for use by projection)
QStringList split(const QString &sep, const QString &str, bool allowEmptyEntries)
const QString & comment() const
Returns the field comment.
Definition: qgsfield.cpp:97
virtual bool setSubsetString(const QString &subset)
Set the string (typically sql) used to define a subset of the layer.
bool toBool() const
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Emit a signal that sets the mode for the progress of the current operation.
QgsLayerTreeLayer * findLayer(const QString &layerId) const
Find layer node representing the map layer specified by its ID. Searches recursively the whole sub-tr...
qint64 QgsFeatureId
Definition: qgsfeature.h:31
const QgsCoordinateReferenceSystem & crs() const
Returns layer's spatial reference system.
iterator insert(const Key &key, const T &value)
static QgsGeometry * fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
bool nextFeature(QgsFeature &f)
This is the base class for vector data providers.
QgsLayerTreeGroup * layerTreeRoot() const
Return pointer to the root (invisible) node of the project's layer tree.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsfeature.h:109
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
QString joinLayerId
Source layer.
void progressStarted()
Emit a signal that processing has started.
QString exportToWkt(const int &precision=17) const
Exports the geometry to WKT.
iterator begin()
bool mkpath(const QString &dirPath) const
Layer tree node points to a map layer.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:77
const T value(const Key &key) const
QByteArray toUtf8() const