31 #include <QDomDocument>
34 #include <QMessageBox>
39 #include <spatialite.h>
46 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
47 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
48 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
49 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
50 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
67 if ( layerIds.isEmpty() )
71 QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
76 int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
77 if ( rc != SQLITE_OK )
79 showWarning(
tr(
"Could not open the spatialite database" ) );
89 for (
int i = 0; i < layerIds.count(); i++ )
103 if ( projectTitle.isEmpty() )
107 projectTitle +=
" (offline)";
147 QList<QgsMapLayer*> offlineLayers;
149 for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
154 offlineLayers << layer;
158 for (
int l = 0; l < offlineLayers.count(); l++ )
166 QString remoteName = layer->
name();
167 remoteName.remove( QRegExp(
" \\(offline\\)$" ) );
181 QList<QgsMapLayer *>() << remoteLayer,
true );
184 QString qgisLayerId = layer->
id();
185 QString sql = QString(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
193 for (
int i = 0; i < commitNo; i++ )
210 sql = QString(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
212 sql = QString(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
214 sql = QString(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
216 sql = QString(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
218 sql = QString(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
222 QString sql = QString(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
233 ( QStringList() << qgisLayerId ) );
237 projectTitle.remove( QRegExp(
" \\(offline\\)$" ) );
252 if ( !sqlite_handle )
257 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns, NULL );
258 if ( ret != SQLITE_OK )
263 for (
int i = 1; i <= rows; i++ )
264 count = atoi( results[( i * columns ) + 0] );
267 sqlite3_free_table( results );
272 bool above41 =
false;
273 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns, NULL );
274 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
276 QString version = QString::fromUtf8( results[1] );
277 QStringList parts = version.split(
" ", QString::SkipEmptyParts );
278 if ( parts.size() >= 1 )
280 QStringList verparts = parts[0].split(
".", QString::SkipEmptyParts );
281 above41 = verparts.size() >= 2 && ( verparts[0].toInt() > 4 || ( verparts[0].toInt() == 4 && verparts[1].toInt() >= 1 ) );
285 sqlite3_free_table( results );
289 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()", NULL, NULL, &errMsg );
291 if ( ret != SQLITE_OK )
293 QString errCause =
tr(
"Unable to initialize SpatialMetadata:\n" );
294 errCause += QString::fromUtf8( errMsg );
296 sqlite3_free( errMsg );
299 spatial_ref_sys_init( sqlite_handle, 0 );
307 QFile newDb( offlineDbPath );
308 if ( newDb.exists() )
310 QFile::remove( offlineDbPath );
315 QFileInfo fullPath = QFileInfo( offlineDbPath );
316 QDir path = fullPath.dir();
319 QDir().mkpath( path.absolutePath( ) );
322 QString dbPath = newDb.fileName();
323 spatialite_init( 0 );
324 ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
328 QString errCause =
tr(
"Could not create a new database\n" );
329 errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
330 sqlite3_close( sqlite_handle );
335 ret = sqlite3_exec( sqlite_handle,
"PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
336 if ( ret != SQLITE_OK )
338 showWarning(
tr(
"Unable to activate FOREIGN_KEY constraints" ) );
339 sqlite3_free( errMsg );
340 sqlite3_close( sqlite_handle );
346 sqlite3_close( sqlite_handle );
354 QString sql =
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
357 sql =
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
360 sql =
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
364 sql =
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
368 sql =
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
372 sql =
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
373 sql +=
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
377 sql =
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
381 sql =
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
385 sql =
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
389 sql =
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
404 QString tableName = layer->
name();
407 QString sql = QString(
"CREATE TABLE '%1' (" ).arg( tableName );
410 for (
int idx = 0; idx < fields.
count(); ++idx )
412 QString dataType =
"";
413 QVariant::Type type = fields[idx].type();
414 if ( type == QVariant::Int || type == QVariant::LongLong )
416 dataType =
"INTEGER";
418 else if ( type == QVariant::Double )
422 else if ( type == QVariant::String )
431 sql += delim + QString(
"'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
437 QString geomType =
"";
444 geomType =
"MULTIPOINT";
447 geomType =
"LINESTRING";
450 geomType =
"MULTILINESTRING";
453 geomType =
"POLYGON";
456 geomType =
"MULTIPOLYGON";
462 QString sqlAddGeom = QString(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
464 .arg( layer->
crs().
authid().startsWith(
"EPSG:", Qt::CaseInsensitive ) ? layer->
crs().
authid().mid( 5 ).toLong() : 0 )
468 QString sqlCreateIndex = QString(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
471 if ( rc == SQLITE_OK )
473 rc =
sqlExec( db, sqlAddGeom );
474 if ( rc == SQLITE_OK )
476 rc =
sqlExec( db, sqlCreateIndex );
480 if ( rc == SQLITE_OK )
484 .arg( offlineDbPath ).arg( tableName ), tableName +
" (offline)",
"spatialite" );
504 QList<QgsMapLayer *>() << newLayer );
524 int featureCount = 1;
526 QList<QgsFeatureId> remoteFeatureIds;
529 remoteFeatureIds << f.
id();
536 for (
int it = 0; it < attrs.count(); ++it )
538 newAttrs[column++] = attrs[it];
553 QList<QgsFeatureId> offlineFeatureIds;
558 offlineFeatureIds << f.
id();
563 int remoteCount = remoteFeatureIds.size();
564 for (
int i = 0; i < remoteCount; i++ )
566 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
578 QStringList() << layer->
id() );
585 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 );
589 QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
592 QMap < QVariant::Type, QString > typeNameLookup;
593 for (
int i = 0; i < nativeTypes.size(); i++ )
601 for (
int i = 0; i < fields.size(); i++ )
605 if ( typeNameLookup.contains( field.
type() ) )
607 QString typeName = typeNameLookup[ field.
type()];
613 showWarning( QString(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
622 QString sql = QString(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
627 QVector<QVariant> defaultValues( remoteFlds.
count() );
628 for (
int i = 0; i < remoteFlds.
count(); ++i )
636 for (
int i = 0; i < newFeatureIds.size(); i++ )
650 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
656 QMap<int, int> attrLookup =
attributeLookup( offlineLayer, remoteLayer );
659 for (
int it = 0; it < attrs.count(); ++it )
661 newAttrs[ attrLookup[ it ] ] = attrs[ it ];
666 for (
int k = 0; k < newAttrs.count(); ++k )
668 if ( newAttrs[k].
isNull() && !defaultValues[k].isNull() )
669 newAttrs[k] = defaultValues[k];
682 QString sql = QString(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
688 for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
699 QString sql = QString(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
704 QMap<int, int> attrLookup =
attributeLookup( offlineLayer, remoteLayer );
706 for (
int i = 0; i < values.size(); i++ )
710 remoteLayer->
changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
718 QString sql = QString(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
723 for (
int i = 0; i < values.size(); i++ )
750 newRemoteFids[ f.
id()] =
true;
758 QString sql = QString(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
761 if ( newRemoteFids.size() != newOfflineFids.size() )
770 for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
772 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
782 QDomElement node = doc.createElement(
"symbology" );
783 doc.appendChild( node );
786 if ( error.isEmpty() )
790 if ( !error.isEmpty() )
802 QMap <
int ,
int > attrLookup;
804 for (
int i = 0; i < remoteAttrs.size(); i++ )
806 attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
814 QMessageBox::warning( NULL,
tr(
"Offline Editing Plugin" ), message );
821 if ( !dbPath.isEmpty() )
823 int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
824 if ( rc != SQLITE_OK )
826 showWarning(
tr(
"Could not open the spatialite logging database" ) );
836 QString sql = QString(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
841 sql =
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
845 sql = QString(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
850 sql = QString(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
853 layerId = newLayerId;
861 QString sql =
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
867 QString sql = QString(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg(
getCommitNo( db ) + 1 );
873 QString sql = QString(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
879 QString sql = QString(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
885 QString sql = QString(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
891 QString sql = QString(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
898 int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
899 if ( rc != SQLITE_OK )
908 sqlite3_stmt* stmt = NULL;
909 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
915 int value = defaultValue;
916 int ret = sqlite3_step( stmt );
917 if ( ret == SQLITE_ROW )
919 value = sqlite3_column_int( stmt, 0 );
921 sqlite3_finalize( stmt );
930 sqlite3_stmt* stmt = NULL;
931 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
937 int ret = sqlite3_step( stmt );
938 while ( ret == SQLITE_ROW )
940 values << sqlite3_column_int( stmt, 0 );
942 ret = sqlite3_step( stmt );
944 sqlite3_finalize( stmt );
951 QList<QgsField> values;
953 sqlite3_stmt* stmt = NULL;
954 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
960 int ret = sqlite3_step( stmt );
961 while ( ret == SQLITE_ROW )
963 QgsField field( QString((
const char* )sqlite3_column_text( stmt, 0 ) ),
964 ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
966 sqlite3_column_int( stmt, 2 ),
967 sqlite3_column_int( stmt, 3 ),
968 QString((
const char* )sqlite3_column_text( stmt, 4 ) ) );
971 ret = sqlite3_step( stmt );
973 sqlite3_finalize( stmt );
982 sqlite3_stmt* stmt = NULL;
983 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
989 int ret = sqlite3_step( stmt );
990 while ( ret == SQLITE_ROW )
992 values << sqlite3_column_int( stmt, 0 );
994 ret = sqlite3_step( stmt );
996 sqlite3_finalize( stmt );
1005 sqlite3_stmt* stmt = NULL;
1006 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1012 int ret = sqlite3_step( stmt );
1013 while ( ret == SQLITE_ROW )
1016 change.
fid = sqlite3_column_int( stmt, 0 );
1017 change.
attr = sqlite3_column_int( stmt, 1 );
1018 change.
value = QString((
const char* )sqlite3_column_text( stmt, 2 ) );
1021 ret = sqlite3_step( stmt );
1023 sqlite3_finalize( stmt );
1032 sqlite3_stmt* stmt = NULL;
1033 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1039 int ret = sqlite3_step( stmt );
1040 while ( ret == SQLITE_ROW )
1043 change.
fid = sqlite3_column_int( stmt, 0 );
1044 change.
geom_wkt = QString((
const char* )sqlite3_column_text( stmt, 1 ) );
1047 ret = sqlite3_step( stmt );
1049 sqlite3_finalize( stmt );
1066 for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1069 QString sql = QString(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1072 .arg( field.
name() )
1073 .arg( field.
type() )
1081 sqlite3_close( db );
1100 QString sql = QString(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.
table() ).arg( addedFeatures.size() );
1102 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1104 QString sql = QString(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1106 .arg( newFeatureIds.at( i ) );
1110 sqlite3_close( db );
1124 for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1129 QString sql = QString(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1134 QString sql = QString(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1141 sqlite3_close( db );
1156 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1165 for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1167 QString sql = QString(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1172 .arg( it.value().toString() );
1178 sqlite3_close( db );
1193 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1202 QString sql = QString(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1213 sqlite3_close( db );