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\\)$" ) );
261 if ( sqlite_handle == NULL )
264 strcpy( sql,
"SELECT Count(*) from sqlite_master" );
265 ret = sqlite3_get_table( sqlite_handle, sql, &results, &rows, &columns, NULL );
266 if ( ret != SQLITE_OK )
272 for ( i = 1; i <= rows; i++ )
273 count = atoi( results[( i * columns ) + 0] );
275 sqlite3_free_table( results );
281 strcpy( sql,
"SELECT InitSpatialMetadata()" );
282 ret = sqlite3_exec( sqlite_handle, sql, NULL, NULL, &errMsg );
283 if ( ret != SQLITE_OK )
285 QString errCause =
tr(
"Unable to initialize SpatialMetadata:\n" );
286 errCause += QString::fromUtf8( errMsg );
288 sqlite3_free( errMsg );
291 spatial_ref_sys_init( sqlite_handle, 0 );
299 QFile newDb( offlineDbPath );
300 if ( newDb.exists() )
302 QFile::remove( offlineDbPath );
307 QFileInfo fullPath = QFileInfo( offlineDbPath );
308 QDir path = fullPath.dir();
311 QDir().mkpath( path.absolutePath( ) );
314 QString dbPath = newDb.fileName();
315 spatialite_init( 0 );
316 ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL );
320 QString errCause =
tr(
"Could not create a new database\n" );
321 errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) );
322 sqlite3_close( sqlite_handle );
327 ret = sqlite3_exec( sqlite_handle,
"PRAGMA foreign_keys = 1", NULL, 0, &errMsg );
328 if ( ret != SQLITE_OK )
330 showWarning(
tr(
"Unable to activate FOREIGN_KEY constraints" ) );
331 sqlite3_free( errMsg );
332 sqlite3_close( sqlite_handle );
338 sqlite3_close( sqlite_handle );
346 QString sql =
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
349 sql =
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
352 sql =
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
356 sql =
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
360 sql =
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
364 sql =
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
365 sql +=
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
369 sql =
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
373 sql =
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
377 sql =
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
381 sql =
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
396 QString tableName = layer->
name();
399 QString sql = QString(
"CREATE TABLE '%1' (" ).arg( tableName );
402 for (
int idx = 0; idx < fields.
count(); ++idx )
404 QString dataType =
"";
405 QVariant::Type type = fields[idx].type();
406 if ( type == QVariant::Int )
408 dataType =
"INTEGER";
410 else if ( type == QVariant::Double )
414 else if ( type == QVariant::String )
423 sql += delim + QString(
"'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
429 QString geomType =
"";
436 geomType =
"MULTIPOINT";
439 geomType =
"LINESTRING";
442 geomType =
"MULTILINESTRING";
445 geomType =
"POLYGON";
448 geomType =
"MULTIPOLYGON";
454 QString sqlAddGeom = QString(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
456 .arg( layer->
crs().
authid().startsWith(
"EPSG:", Qt::CaseInsensitive ) ? layer->
crs().
authid().mid( 5 ).toLong() : 0 )
460 QString sqlCreateIndex = QString(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
463 if ( rc == SQLITE_OK )
465 rc =
sqlExec( db, sqlAddGeom );
466 if ( rc == SQLITE_OK )
468 rc =
sqlExec( db, sqlCreateIndex );
472 if ( rc == SQLITE_OK )
476 .arg( offlineDbPath ).arg( tableName ), tableName +
" (offline)",
"spatialite" );
496 QList<QgsMapLayer *>() << newLayer );
516 int featureCount = 1;
518 QList<QgsFeatureId> remoteFeatureIds;
521 remoteFeatureIds << f.
id();
528 for (
int it = 0; it < attrs.count(); ++it )
530 newAttrs[column++] = attrs[it];
545 QList<QgsFeatureId> offlineFeatureIds;
550 offlineFeatureIds << f.
id();
555 int remoteCount = remoteFeatureIds.size();
556 for (
int i = 0; i < remoteCount; i++ )
558 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
570 QStringList() << layer->
id() );
577 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 );
581 QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
584 QMap < QVariant::Type, QString > typeNameLookup;
585 for (
int i = 0; i < nativeTypes.size(); i++ )
593 for (
int i = 0; i < fields.size(); i++ )
597 if ( typeNameLookup.contains( field.
type() ) )
599 QString typeName = typeNameLookup[ field.
type()];
605 showWarning( QString(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
614 QString sql = QString(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
619 for (
int i = 0; i < newFeatureIds.size(); i++ )
633 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
639 QMap<int, int> attrLookup =
attributeLookup( offlineLayer, remoteLayer );
642 for (
int it = 0; it < attrs.count(); ++it )
644 newAttrs[ attrLookup[ it ] ] = attrs[ it ];
656 QString sql = QString(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
662 for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
673 QString sql = QString(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
678 QMap<int, int> attrLookup =
attributeLookup( offlineLayer, remoteLayer );
680 for (
int i = 0; i < values.size(); i++ )
684 remoteLayer->
changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value, false );
692 QString sql = QString(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
697 for (
int i = 0; i < values.size(); i++ )
724 newRemoteFids[ f.
id()] =
true;
732 QString sql = QString(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
735 if ( newRemoteFids.size() != newOfflineFids.size() )
744 for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
746 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
756 QDomElement node = doc.createElement(
"symbology" );
757 doc.appendChild( node );
760 if ( error.isEmpty() )
764 if ( !error.isEmpty() )
776 QMap <
int ,
int > attrLookup;
778 for (
int i = 0; i < remoteAttrs.size(); i++ )
780 attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
788 QMessageBox::warning( NULL,
tr(
"Offline Editing Plugin" ), message );
795 if ( !dbPath.isEmpty() )
797 int rc = sqlite3_open( dbPath.toUtf8().constData(), &db );
798 if ( rc != SQLITE_OK )
800 showWarning(
tr(
"Could not open the spatialite logging database" ) );
810 QString sql = QString(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
815 sql =
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
819 sql = QString(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
824 sql = QString(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
827 layerId = newLayerId;
835 QString sql =
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
841 QString sql = QString(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg(
getCommitNo( db ) + 1 );
847 QString sql = QString(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
853 QString sql = QString(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
859 QString sql = QString(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
865 QString sql = QString(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
872 int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
873 if ( rc != SQLITE_OK )
882 sqlite3_stmt* stmt = NULL;
883 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
889 int value = defaultValue;
890 int ret = sqlite3_step( stmt );
891 if ( ret == SQLITE_ROW )
893 value = sqlite3_column_int( stmt, 0 );
895 sqlite3_finalize( stmt );
904 sqlite3_stmt* stmt = NULL;
905 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
911 int ret = sqlite3_step( stmt );
912 while ( ret == SQLITE_ROW )
914 values << sqlite3_column_int( stmt, 0 );
916 ret = sqlite3_step( stmt );
918 sqlite3_finalize( stmt );
925 QList<QgsField> values;
927 sqlite3_stmt* stmt = NULL;
928 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
934 int ret = sqlite3_step( stmt );
935 while ( ret == SQLITE_ROW )
937 QgsField field( QString((
const char* )sqlite3_column_text( stmt, 0 ) ),
938 ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
940 sqlite3_column_int( stmt, 2 ),
941 sqlite3_column_int( stmt, 3 ),
942 QString((
const char* )sqlite3_column_text( stmt, 4 ) ) );
945 ret = sqlite3_step( stmt );
947 sqlite3_finalize( stmt );
956 sqlite3_stmt* stmt = NULL;
957 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
963 int ret = sqlite3_step( stmt );
964 while ( ret == SQLITE_ROW )
966 values << sqlite3_column_int( stmt, 0 );
968 ret = sqlite3_step( stmt );
970 sqlite3_finalize( stmt );
979 sqlite3_stmt* stmt = NULL;
980 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
986 int ret = sqlite3_step( stmt );
987 while ( ret == SQLITE_ROW )
990 change.
fid = sqlite3_column_int( stmt, 0 );
991 change.
attr = sqlite3_column_int( stmt, 1 );
992 change.
value = QString((
const char* )sqlite3_column_text( stmt, 2 ) );
995 ret = sqlite3_step( stmt );
997 sqlite3_finalize( stmt );
1006 sqlite3_stmt* stmt = NULL;
1007 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
1013 int ret = sqlite3_step( stmt );
1014 while ( ret == SQLITE_ROW )
1017 change.
fid = sqlite3_column_int( stmt, 0 );
1018 change.
geom_wkt = QString((
const char* )sqlite3_column_text( stmt, 1 ) );
1021 ret = sqlite3_step( stmt );
1023 sqlite3_finalize( stmt );
1040 for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
1043 QString sql = QString(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1046 .arg( field.
name() )
1047 .arg( field.
type() )
1055 sqlite3_close( db );
1074 QString sql = QString(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.
table() ).arg( addedFeatures.size() );
1076 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1078 QString sql = QString(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1080 .arg( newFeatureIds.at( i ) );
1084 sqlite3_close( db );
1098 for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
1103 QString sql = QString(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
1108 QString sql = QString(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1115 sqlite3_close( db );
1130 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1139 for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
1141 QString sql = QString(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1146 .arg( it.value().toString() );
1152 sqlite3_close( db );
1167 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1176 QString sql = QString(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1187 sqlite3_close( db );