QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 
33 #include <QDir>
34 #include <QDomDocument>
35 #include <QDomNode>
36 #include <QFile>
37 #include <QMessageBox>
38 
39 extern "C"
40 {
41 #include <sqlite3.h>
42 #include <spatialite.h>
43 }
44 
45 // TODO: DEBUG
46 #include <QDebug>
47 // END
48 
49 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
50 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
51 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
52 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
53 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
54 
56 {
57  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
58 }
59 
61 {
62 }
63 
68 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds )
69 {
70  if ( layerIds.isEmpty() )
71  {
72  return false;
73  }
74  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
75  if ( createSpatialiteDB( dbPath ) )
76  {
77  spatialite_init( 0 );
78  sqlite3* db;
79  int rc = 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
136  QMap<QString, QgsVectorJoinList >::ConstIterator it;
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 
159  sqlite3_close( db );
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()->title( 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;
208  QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
209  for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
210  {
211  QgsMapLayer* layer = layer_it.value();
212  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
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()->title( 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  spatialite_init( 0 );
384  ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
385  if ( ret )
386  {
387  // an error occurred
388  QString errCause = tr( "Could not create a new database\n" );
389  errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
390  sqlite3_close( sqlite_handle );
391  showWarning( errCause );
392  return false;
393  }
394  // activating Foreign Key constraints
395  ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
396  if ( ret != SQLITE_OK )
397  {
398  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
399  sqlite3_free( errMsg );
400  sqlite3_close( sqlite_handle );
401  return false;
402  }
403  initializeSpatialMetadata( sqlite_handle );
404 
405  // all done: closing the DB connection
406  sqlite3_close( sqlite_handle );
407 
408  return true;
409 }
410 
411 void QgsOfflineEditing::createLoggingTables( sqlite3* db )
412 {
413  // indices
414  QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
415  sqlExec( db, sql );
416 
417  sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
418  sqlExec( db, sql );
419 
420  sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
421  sqlExec( db, sql );
422 
423  // layername <-> layer id
424  sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
425  sqlExec( db, sql );
426 
427  // offline fid <-> remote fid
428  sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
429  sqlExec( db, sql );
430 
431  // added attributes
432  sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
433  sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
434  sqlExec( db, sql );
435 
436  // added features
437  sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
438  sqlExec( db, sql );
439 
440  // removed features
441  sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
442  sqlExec( db, sql );
443 
444  // feature updates
445  sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
446  sqlExec( db, sql );
447 
448  // geometry updates
449  sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
450  sqlExec( db, sql );
451 
452  /* TODO: other logging tables
453  - attr delete (not supported by SpatiaLite provider)
454  */
455 }
456 
457 QgsVectorLayer* QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
458 {
459  if ( layer == 0 )
460  {
461  return 0;
462  }
463 
464  QString tableName = layer->id();
465 
466  // create table
467  QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
468  QString delim = "";
469  const QgsFields& fields = layer->dataProvider()->fields();
470  for ( int idx = 0; idx < fields.count(); ++idx )
471  {
472  QString dataType = "";
473  QVariant::Type type = fields[idx].type();
474  if ( type == QVariant::Int || type == QVariant::LongLong )
475  {
476  dataType = "INTEGER";
477  }
478  else if ( type == QVariant::Double )
479  {
480  dataType = "REAL";
481  }
482  else if ( type == QVariant::String )
483  {
484  dataType = "TEXT";
485  }
486  else
487  {
488  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( fields[idx].name() ).arg( QVariant::typeToName( type ) ) );
489  }
490 
491  sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
492  delim = ",";
493  }
494  sql += ")";
495 
496  int rc = sqlExec( db, sql );
497 
498  // add geometry column
499  if ( layer->hasGeometryType() )
500  {
501  QString geomType = "";
502  switch ( layer->wkbType() )
503  {
504  case QGis::WKBPoint:
505  geomType = "POINT";
506  break;
507  case QGis::WKBMultiPoint:
508  geomType = "MULTIPOINT";
509  break;
510  case QGis::WKBLineString:
511  geomType = "LINESTRING";
512  break;
514  geomType = "MULTILINESTRING";
515  break;
516  case QGis::WKBPolygon:
517  geomType = "POLYGON";
518  break;
520  geomType = "MULTIPOLYGON";
521  break;
522  default:
523  showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
524  break;
525  };
526  QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
527  .arg( tableName )
528  .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
529  .arg( geomType );
530 
531  // create spatial index
532  QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
533 
534  if ( rc == SQLITE_OK )
535  {
536  rc = sqlExec( db, sqlAddGeom );
537  if ( rc == SQLITE_OK )
538  {
539  rc = sqlExec( db, sqlCreateIndex );
540  }
541  }
542  }
543 
544  if ( rc == SQLITE_OK )
545  {
546  // add new layer
547  QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'%3 sql=" )
548  .arg( offlineDbPath )
549  .arg( tableName ).arg( layer->hasGeometryType() ? "(Geometry)" : "" ),
550  layer->name() + " (offline)", "spatialite" );
551  if ( newLayer->isValid() )
552  {
553  // mark as offline layer
555 
556  // store original layer source
559 
560  // register this layer with the central layers registry
562  QList<QgsMapLayer *>() << newLayer );
563 
564  // copy style
566  bool hasLabels = layer->hasLabelsEnabled();
568  if ( !hasLabels )
569  {
570  // NOTE: copy symbology before adding the layer so it is displayed correctly
571  copySymbology( layer, newLayer );
572  }
573 
575  // Find the parent group of the original layer
576  QgsLayerTreeLayer* layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
577  if ( layerTreeLayer )
578  {
579  QgsLayerTreeGroup* parentTreeGroup = qobject_cast<QgsLayerTreeGroup*>( layerTreeLayer->parent() );
580  if ( parentTreeGroup )
581  {
582  int index = parentTreeGroup->children().indexOf( layerTreeLayer );
583  // Move the new layer from the root group to the new group
584  QgsLayerTreeLayer* newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
585  if ( newLayerTreeLayer )
586  {
587  QgsLayerTreeNode* newLayerTreeLayerClone = newLayerTreeLayer->clone();
588  QgsLayerTreeGroup* grp = qobject_cast<QgsLayerTreeGroup*>( newLayerTreeLayer->parent() );
589  parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
590  if ( grp )
591  grp->removeChildNode( newLayerTreeLayer );
592  }
593  }
594  }
595 
596  if ( hasLabels )
597  {
598  // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
599  copySymbology( layer, newLayer );
600  }
601 
602  // copy features
603  newLayer->startEditing();
604  QgsFeature f;
605 
606  // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
607  layer->setSubsetString( layer->subsetString() );
608 
609  QgsFeatureIterator fit = layer->dataProvider()->getFeatures();
610 
612  int featureCount = 1;
613 
614  QList<QgsFeatureId> remoteFeatureIds;
615  while ( fit.nextFeature( f ) )
616  {
617  remoteFeatureIds << f.id();
618 
619  // NOTE: Spatialite provider ignores position of geometry column
620  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
621  int column = 0;
622  QgsAttributes attrs = f.attributes();
623  QgsAttributes newAttrs( attrs.count() );
624  for ( int it = 0; it < attrs.count(); ++it )
625  {
626  newAttrs[column++] = attrs[it];
627  }
628  f.setAttributes( newAttrs );
629 
630  newLayer->addFeature( f, false );
631 
632  emit progressUpdated( featureCount++ );
633  }
634  if ( newLayer->commitChanges() )
635  {
637  featureCount = 1;
638 
639  // update feature id lookup
640  int layerId = getOrCreateLayerId( db, newLayer->id() );
641  QList<QgsFeatureId> offlineFeatureIds;
642 
643  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
644  while ( fit.nextFeature( f ) )
645  {
646  offlineFeatureIds << f.id();
647  }
648 
649  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
650  sqlExec( db, "BEGIN" );
651  int remoteCount = remoteFeatureIds.size();
652  for ( int i = 0; i < remoteCount; i++ )
653  {
654  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
655  emit progressUpdated( featureCount++ );
656  }
657  sqlExec( db, "COMMIT" );
658  }
659  else
660  {
661  showWarning( newLayer->commitErrors().join( "\n" ) );
662  }
663 
664  // remove remote layer
666  QStringList() << layer->id() );
667  }
668  return newLayer;
669  }
670  return 0;
671 }
672 
673 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
674 {
675  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 );
676  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
677 
678  const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
679  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
680 
681  // NOTE: uses last matching QVariant::Type of nativeTypes
682  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
683  for ( int i = 0; i < nativeTypes.size(); i++ )
684  {
685  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
686  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
687  }
688 
689  emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
690 
691  for ( int i = 0; i < fields.size(); i++ )
692  {
693  // lookup typename from layer provider
694  QgsField field = fields[i];
695  if ( typeNameLookup.contains( field.type() ) )
696  {
697  QString typeName = typeNameLookup[ field.type()];
698  field.setTypeName( typeName );
699  remoteLayer->addAttribute( field );
700  }
701  else
702  {
703  showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
704  }
705 
706  emit progressUpdated( i + 1 );
707  }
708 }
709 
710 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
711 {
712  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
713  QList<int> newFeatureIds = sqlQueryInts( db, sql );
714 
715  // get default value for each field
716  const QgsFields& remoteFlds = remoteLayer->pendingFields();
717  QVector<QVariant> defaultValues( remoteFlds.count() );
718  for ( int i = 0; i < remoteFlds.count(); ++i )
719  {
720  if ( remoteFlds.fieldOrigin( i ) == QgsFields::OriginProvider )
721  defaultValues[i] = remoteLayer->dataProvider()->defaultValue( remoteFlds.fieldOriginIndex( i ) );
722  }
723 
724  // get new features from offline layer
725  QgsFeatureList features;
726  for ( int i = 0; i < newFeatureIds.size(); i++ )
727  {
728  QgsFeature feature;
729  if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
730  {
731  features << feature;
732  }
733  }
734 
735  // copy features to remote layer
736  emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
737 
738  int i = 1;
739  int newAttrsCount = remoteLayer->pendingFields().count();
740  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
741  {
742  QgsFeature f = *it;
743 
744  // NOTE: Spatialite provider ignores position of geometry column
745  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
746  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
747  QgsAttributes newAttrs( newAttrsCount );
748  QgsAttributes attrs = f.attributes();
749  for ( int it = 0; it < attrs.count(); ++it )
750  {
751  newAttrs[ attrLookup[ it ] ] = attrs[ it ];
752  }
753 
754  // try to use default value from the provider
755  // (important especially e.g. for postgis primary key generated from a sequence)
756  for ( int k = 0; k < newAttrs.count(); ++k )
757  {
758  if ( newAttrs[k].isNull() && !defaultValues[k].isNull() )
759  newAttrs[k] = defaultValues[k];
760  }
761 
762  f.setAttributes( newAttrs );
763 
764  remoteLayer->addFeature( f, false );
765 
766  emit progressUpdated( i++ );
767  }
768 }
769 
770 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
771 {
772  QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
773  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
774 
775  emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
776 
777  int i = 1;
778  for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
779  {
780  QgsFeatureId fid = remoteFid( db, layerId, *it );
781  remoteLayer->deleteFeature( fid );
782 
783  emit progressUpdated( i++ );
784  }
785 }
786 
787 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
788 {
789  QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
790  AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
791 
792  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
793 
794  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
795 
796  for ( int i = 0; i < values.size(); i++ )
797  {
798  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
799 
800  remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
801 
802  emit progressUpdated( i + 1 );
803  }
804 }
805 
806 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
807 {
808  QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
809  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
810 
812 
813  for ( int i = 0; i < values.size(); i++ )
814  {
815  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
816  remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) );
817 
818  emit progressUpdated( i + 1 );
819  }
820 }
821 
822 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
823 {
824  // update fid lookup for added features
825 
826  // get remote added fids
827  // NOTE: use QMap for sorted fids
828  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
829  QgsFeature f;
830 
831  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
832 
834 
835  int i = 1;
836  while ( fit.nextFeature( f ) )
837  {
838  if ( offlineFid( db, layerId, f.id() ) == -1 )
839  {
840  newRemoteFids[ f.id()] = true;
841  }
842 
843  emit progressUpdated( i++ );
844  }
845 
846  // get local added fids
847  // NOTE: fids are sorted
848  QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
849  QList<int> newOfflineFids = sqlQueryInts( db, sql );
850 
851  if ( newRemoteFids.size() != newOfflineFids.size() )
852  {
853  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
854  }
855  else
856  {
857  // add new fid lookups
858  i = 0;
859  sqlExec( db, "BEGIN" );
860  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
861  {
862  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
863  }
864  sqlExec( db, "COMMIT" );
865  }
866 }
867 
868 void QgsOfflineEditing::copySymbology( QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
869 {
870  QString error;
871  QDomDocument doc;
872  sourceLayer->exportNamedStyle( doc, error );
873 
874  if ( error.isEmpty() )
875  {
876  targetLayer->importNamedStyle( doc, error );
877  }
878  if ( !error.isEmpty() )
879  {
880  showWarning( error );
881  }
882 }
883 
884 // NOTE: use this to map column indices in case the remote geometry column is not last
885 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
886 {
887  const QgsAttributeList& offlineAttrs = offlineLayer->pendingAllAttributesList();
888  const QgsAttributeList& remoteAttrs = remoteLayer->pendingAllAttributesList();
889 
890  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
891  // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
892  for ( int i = 0; i < remoteAttrs.size(); i++ )
893  {
894  attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
895  }
896 
897  return attrLookup;
898 }
899 
900 void QgsOfflineEditing::showWarning( const QString& message )
901 {
902  emit warning( tr( "Offline Editing Plugin" ), message );
903 }
904 
905 sqlite3* QgsOfflineEditing::openLoggingDb()
906 {
907  sqlite3* db = NULL;
909  if ( !dbPath.isEmpty() )
910  {
911  int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
912  if ( rc != SQLITE_OK )
913  {
914  showWarning( tr( "Could not open the spatialite logging database" ) );
915  sqlite3_close( db );
916  db = NULL;
917  }
918  }
919  return db;
920 }
921 
922 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
923 {
924  QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
925  int layerId = sqlQueryInt( db, sql, -1 );
926  if ( layerId == -1 )
927  {
928  // next layer id
929  sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
930  int newLayerId = sqlQueryInt( db, sql, -1 );
931 
932  // insert layer
933  sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
934  sqlExec( db, sql );
935 
936  // increase layer_id
937  // TODO: use trigger for auto increment?
938  sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
939  sqlExec( db, sql );
940 
941  layerId = newLayerId;
942  }
943 
944  return layerId;
945 }
946 
947 int QgsOfflineEditing::getCommitNo( sqlite3* db )
948 {
949  QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
950  return sqlQueryInt( db, sql, -1 );
951 }
952 
953 void QgsOfflineEditing::increaseCommitNo( sqlite3* db )
954 {
955  QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
956  sqlExec( db, sql );
957 }
958 
959 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
960 {
961  QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
962  sqlExec( db, sql );
963 }
964 
965 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, QgsFeatureId offlineFid )
966 {
967  QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
968  return sqlQueryInt( db, sql, -1 );
969 }
970 
971 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, QgsFeatureId remoteFid )
972 {
973  QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
974  return sqlQueryInt( db, sql, -1 );
975 }
976 
977 bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, QgsFeatureId fid )
978 {
979  QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
980  return ( sqlQueryInt( db, sql, 0 ) > 0 );
981 }
982 
983 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
984 {
985  char * errmsg;
986  int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
987  if ( rc != SQLITE_OK )
988  {
989  showWarning( errmsg );
990  }
991  return rc;
992 }
993 
994 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
995 {
996  sqlite3_stmt* stmt = NULL;
997  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
998  {
999  showWarning( sqlite3_errmsg( db ) );
1000  return defaultValue;
1001  }
1002 
1003  int value = defaultValue;
1004  int ret = sqlite3_step( stmt );
1005  if ( ret == SQLITE_ROW )
1006  {
1007  value = sqlite3_column_int( stmt, 0 );
1008  }
1009  sqlite3_finalize( stmt );
1010 
1011  return value;
1012 }
1013 
1014 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
1015 {
1016  QList<int> values;
1017 
1018  sqlite3_stmt* stmt = NULL;
1019  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1020  {
1021  showWarning( sqlite3_errmsg( db ) );
1022  return values;
1023  }
1024 
1025  int ret = sqlite3_step( stmt );
1026  while ( ret == SQLITE_ROW )
1027  {
1028  values << sqlite3_column_int( stmt, 0 );
1029 
1030  ret = sqlite3_step( stmt );
1031  }
1032  sqlite3_finalize( stmt );
1033 
1034  return values;
1035 }
1036 
1037 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
1038 {
1039  QList<QgsField> values;
1040 
1041  sqlite3_stmt* stmt = NULL;
1042  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1043  {
1044  showWarning( sqlite3_errmsg( db ) );
1045  return values;
1046  }
1047 
1048  int ret = sqlite3_step( stmt );
1049  while ( ret == SQLITE_ROW )
1050  {
1051  QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ),
1052  ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
1053  "", // typeName
1054  sqlite3_column_int( stmt, 2 ),
1055  sqlite3_column_int( stmt, 3 ),
1056  QString(( const char* )sqlite3_column_text( stmt, 4 ) ) );
1057  values << field;
1058 
1059  ret = sqlite3_step( stmt );
1060  }
1061  sqlite3_finalize( stmt );
1062 
1063  return values;
1064 }
1065 
1066 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql )
1067 {
1068  QgsFeatureIds values;
1069 
1070  sqlite3_stmt* stmt = NULL;
1071  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1072  {
1073  showWarning( sqlite3_errmsg( db ) );
1074  return values;
1075  }
1076 
1077  int ret = sqlite3_step( stmt );
1078  while ( ret == SQLITE_ROW )
1079  {
1080  values << sqlite3_column_int( stmt, 0 );
1081 
1082  ret = sqlite3_step( stmt );
1083  }
1084  sqlite3_finalize( stmt );
1085 
1086  return values;
1087 }
1088 
1089 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql )
1090 {
1091  AttributeValueChanges values;
1092 
1093  sqlite3_stmt* stmt = NULL;
1094  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1095  {
1096  showWarning( sqlite3_errmsg( db ) );
1097  return values;
1098  }
1099 
1100  int ret = sqlite3_step( stmt );
1101  while ( ret == SQLITE_ROW )
1102  {
1103  AttributeValueChange change;
1104  change.fid = sqlite3_column_int( stmt, 0 );
1105  change.attr = sqlite3_column_int( stmt, 1 );
1106  change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) );
1107  values << change;
1108 
1109  ret = sqlite3_step( stmt );
1110  }
1111  sqlite3_finalize( stmt );
1112 
1113  return values;
1114 }
1115 
1116 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql )
1117 {
1118  GeometryChanges values;
1119 
1120  sqlite3_stmt* stmt = NULL;
1121  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1122  {
1123  showWarning( sqlite3_errmsg( db ) );
1124  return values;
1125  }
1126 
1127  int ret = sqlite3_step( stmt );
1128  while ( ret == SQLITE_ROW )
1129  {
1130  GeometryChange change;
1131  change.fid = sqlite3_column_int( stmt, 0 );
1132  change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) );
1133  values << change;
1134 
1135  ret = sqlite3_step( stmt );
1136  }
1137  sqlite3_finalize( stmt );
1138 
1139  return values;
1140 }
1141 
1142 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
1143 {
1144  sqlite3* db = openLoggingDb();
1145  if ( db == NULL )
1146  {
1147  return;
1148  }
1149 
1150  // insert log
1151  int layerId = getOrCreateLayerId( db, qgisLayerId );
1152  int commitNo = getCommitNo( db );
1153 
1154  for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1155  {
1156  QgsField field = *it;
1157  QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1158  .arg( layerId )
1159  .arg( commitNo )
1160  .arg( field.name() )
1161  .arg( field.type() )
1162  .arg( field.length() )
1163  .arg( field.precision() )
1164  .arg( field.comment() );
1165  sqlExec( db, sql );
1166  }
1167 
1168  increaseCommitNo( db );
1169  sqlite3_close( db );
1170 }
1171 
1172 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
1173 {
1174  sqlite3* db = openLoggingDb();
1175  if ( db == NULL )
1176  {
1177  return;
1178  }
1179 
1180  // insert log
1181  int layerId = getOrCreateLayerId( db, qgisLayerId );
1182 
1183  // get new feature ids from db
1184  QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
1185  QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
1186 
1187  // only store feature ids
1188  QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
1189  QList<int> newFeatureIds = sqlQueryInts( db, sql );
1190  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1191  {
1192  QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1193  .arg( layerId )
1194  .arg( newFeatureIds.at( i ) );
1195  sqlExec( db, sql );
1196  }
1197 
1198  sqlite3_close( db );
1199 }
1200 
1201 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
1202 {
1203  sqlite3* db = openLoggingDb();
1204  if ( db == NULL )
1205  {
1206  return;
1207  }
1208 
1209  // insert log
1210  int layerId = getOrCreateLayerId( db, qgisLayerId );
1211 
1212  for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1213  {
1214  if ( isAddedFeature( db, layerId, *it ) )
1215  {
1216  // remove from added features log
1217  QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1218  sqlExec( db, sql );
1219  }
1220  else
1221  {
1222  QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1223  .arg( layerId )
1224  .arg( *it );
1225  sqlExec( db, sql );
1226  }
1227  }
1228 
1229  sqlite3_close( db );
1230 }
1231 
1232 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
1233 {
1234  sqlite3* db = openLoggingDb();
1235  if ( db == NULL )
1236  {
1237  return;
1238  }
1239 
1240  // insert log
1241  int layerId = getOrCreateLayerId( db, qgisLayerId );
1242  int commitNo = getCommitNo( db );
1243 
1244  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1245  {
1246  QgsFeatureId fid = cit.key();
1247  if ( isAddedFeature( db, layerId, fid ) )
1248  {
1249  // skip added features
1250  continue;
1251  }
1252  QgsAttributeMap attrMap = cit.value();
1253  for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1254  {
1255  QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1256  .arg( layerId )
1257  .arg( commitNo )
1258  .arg( fid )
1259  .arg( it.key() ) // attr
1260  .arg( it.value().toString() ); // value
1261  sqlExec( db, sql );
1262  }
1263  }
1264 
1265  increaseCommitNo( db );
1266  sqlite3_close( db );
1267 }
1268 
1269 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
1270 {
1271  sqlite3* db = openLoggingDb();
1272  if ( db == NULL )
1273  {
1274  return;
1275  }
1276 
1277  // insert log
1278  int layerId = getOrCreateLayerId( db, qgisLayerId );
1279  int commitNo = getCommitNo( db );
1280 
1281  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1282  {
1283  QgsFeatureId fid = it.key();
1284  if ( isAddedFeature( db, layerId, fid ) )
1285  {
1286  // skip added features
1287  continue;
1288  }
1289  QgsGeometry geom = it.value();
1290  QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1291  .arg( layerId )
1292  .arg( commitNo )
1293  .arg( fid )
1294  .arg( geom.exportToWkt() );
1295  sqlExec( db, sql );
1296 
1297  // TODO: use WKB instead of WKT?
1298  }
1299 
1300  increaseCommitNo( db );
1301  sqlite3_close( db );
1302 }
1303 
1304 void QgsOfflineEditing::startListenFeatureChanges()
1305 {
1306  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1307  // enable logging
1308  connect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1309  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1310  connect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1311  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1312  connect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1313  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1314  connect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1315  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1316  connect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1317  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1318 }
1319 
1320 void QgsOfflineEditing::stopListenFeatureChanges()
1321 {
1322  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1323  // disable logging
1324  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
1325  this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
1326  disconnect( vLayer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
1327  this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
1328  disconnect( vLayer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
1329  this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
1330  disconnect( vLayer->editBuffer(), SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
1331  this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
1332  disconnect( vLayer->editBuffer(), SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
1333  this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
1334 }
1335 
1336 void QgsOfflineEditing::layerAdded( QgsMapLayer* layer )
1337 {
1338  // detect offline layer
1339  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
1340  {
1341  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer *>( layer );
1342  connect( vLayer, SIGNAL( editingStarted() ), this, SLOT( startListenFeatureChanges() ) );
1343  connect( vLayer, SIGNAL( editingStopped() ), this, SLOT( stopListenFeatureChanges() ) );
1344  }
1345 }
1346 
1347 
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:100
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:59
const QList< QgsVectorJoinInfo > & vectorJoins() const
Wrapper for iterator of features from vector data provider or vector layer.
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:315
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
QMap< int, QVariant > QgsAttributeMap
Definition: qgsfeature.h:98
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds)
convert current project for offline editing
void setTypeName(const QString &typ)
Set the field type.
Definition: qgsfield.cpp:99
bool deleteFeature(QgsFeatureId fid)
delete a feature from the layer (but does not commit it)
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:317
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:322
#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.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
static QgsMapLayerRegistry * instance()
Definition: qgssingleton.h:23
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:461
#define CUSTOM_PROPERTY_REMOTE_SOURCE
int precision() const
Gets the precision of the field.
Definition: qgsfield.cpp:79
Container of fields for a vector layer.
Definition: qgsfield.h:172
void setAttributes(const QgsAttributes &attrs)
Definition: qgsfeature.h:144
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group. The node will be deleted.
bool addFeature(QgsFeature &f, bool alsoUpdateExtent=true)
Adds a feature.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
QGis::WkbType wkbType() const
Returns the WKBType or WKBUnknown in case of error.
bool isOfflineProject()
return true if current project is offline
const QString & name() const
Get the display name of the layer.
field comes from the underlying data provider of the vector layer (originIndex = index in provider's ...
Definition: qgsfield.h:179
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...
void progressStopped()
emit a signal that processing of all layers has finished
const QString & source() const
Returns the source for the layer.
QList< QgsMapLayer * > addMapLayers(QList< QgsMapLayer * > theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
QgsLayerTreeNode * parent()
Get pointer to the parent. If parent is a null pointer, the node is a root node.
int fieldOriginIndex(int fieldIdx) const
Get field's origin index (its meaning is specific to each type of origin)
Definition: qgsfield.h:236
const QList< NativeType > & nativeTypes() const
Returns the names of the supported types.
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
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, QVariant value, bool emitSignal)
Changes an attribute value (but does not commit it)
const QgsAttributes & attributes() const
Definition: qgsfeature.h:142
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.
Definition: qgsmaplayer.cpp:98
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.h:214
bool changeGeometry(QgsFeatureId fid, QgsGeometry *geom)
change feature's geometry
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:33
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg)
Import the properties of this layer from a QDomDocument.
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()
#define PROJECT_ENTRY_SCOPE_OFFLINE
const QStringList & commitErrors()
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
struct sqlite3 sqlite3
virtual void reload() override
Synchronises with changes in the datasource.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:462
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.
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
virtual long featureCount() const
Number of features in the layer.
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
QgsAttributeList pendingAllAttributesList()
returns list of attributes
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...
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:312
void removeMapLayers(QStringList theLayerIds)
Remove a set of layers from the registry.
void title(const QString &title)
Every project has an associated title string.
Definition: qgsproject.cpp:360
QVector< QVariant > QgsAttributes
Definition: qgsfeature.h:100
void synchronize()
synchronize to remote layers
virtual bool setSubsetString(QString subset)
Set the string (typically sql) used to define a subset of the layer.
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:351
QString table() const
int length() const
Gets the length of the field.
Definition: qgsfield.cpp:74
Q_DECL_DEPRECATED bool hasLabelsEnabled() const
Label is on.
FieldOrigin fieldOrigin(int fieldIdx) const
Get field's origin (value from an enumeration)
Definition: qgsfield.cpp:218
const QMap< QString, QgsMapLayer * > & mapLayers()
Retrieve the mapLayers collection (mainly intended for use by projection)
QList< QgsVectorJoinInfo > QgsVectorJoinList
const QString & comment() const
Returns the field comment.
Definition: qgsfield.cpp:84
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:30
static QgsGeometry * fromWkt(QString wkt)
static method that creates geometry from Wkt
const QgsCoordinateReferenceSystem & crs() const
Returns layer's spatial reference system.
QgsMapLayer * mapLayer(QString theLayerId)
Retrieve a pointer to a loaded layer by id.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
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.
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 ...
#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.
bool isNull(const QVariant &v)
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:64
#define tr(sourceText)