Example #1
0
void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
{
  if ( layer == NULL )
  {
    return;
  }

  QString tableName = layer->name();

  // create table
  QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
  QString delim = "";
  const QgsFields& fields = layer->dataProvider()->fields();
  for ( int idx = 0; idx < fields.count(); ++idx )
  {
    QString dataType = "";
    QVariant::Type type = fields[idx].type();
    if ( type == QVariant::Int )
    {
      dataType = "INTEGER";
    }
    else if ( type == QVariant::Double )
    {
      dataType = "REAL";
    }
    else if ( type == QVariant::String )
    {
      dataType = "TEXT";
    }
    else
    {
      showWarning( tr( "Unknown data type %1" ).arg( type ) );
    }

    sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType );
    delim = ",";
  }
  sql += ")";

  // add geometry column
  QString geomType = "";
  switch ( layer->wkbType() )
  {
    case QGis::WKBPoint:
      geomType = "POINT";
      break;
    case QGis::WKBMultiPoint:
      geomType = "MULTIPOINT";
      break;
    case QGis::WKBLineString:
      geomType = "LINESTRING";
      break;
    case QGis::WKBMultiLineString:
      geomType = "MULTILINESTRING";
      break;
    case QGis::WKBPolygon:
      geomType = "POLYGON";
      break;
    case QGis::WKBMultiPolygon:
      geomType = "MULTIPOLYGON";
      break;
    default:
      showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) );
      break;
  };
  QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
                       .arg( tableName )
                       .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 )
                       .arg( geomType );

  // create spatial index
  QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );

  int rc = sqlExec( db, sql );
  if ( rc == SQLITE_OK )
  {
    rc = sqlExec( db, sqlAddGeom );
    if ( rc == SQLITE_OK )
    {
      rc = sqlExec( db, sqlCreateIndex );
    }
  }

  if ( rc == SQLITE_OK )
  {
    // add new layer
    QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" )
        .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" );
    if ( newLayer->isValid() )
    {
      // mark as offline layer
      newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true );

      // store original layer source
      newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() );
      newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() );

      // copy style
      bool hasLabels = layer->hasLabelsEnabled();
      if ( !hasLabels )
      {
        // NOTE: copy symbology before adding the layer so it is displayed correctly
        copySymbology( layer, newLayer );
      }

      // register this layer with the central layers registry
      QgsMapLayerRegistry::instance()->addMapLayers(
        QList<QgsMapLayer *>() << newLayer );

      if ( hasLabels )
      {
        // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
        copySymbology( layer, newLayer );
      }

      // TODO: layer order

      // copy features
      newLayer->startEditing();
      QgsFeature f;

      // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
      layer->setSubsetString( "" );

      QgsFeatureIterator fit = layer->getFeatures();

      emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->featureCount() );
      int featureCount = 1;

      QList<QgsFeatureId> remoteFeatureIds;
      while ( fit.nextFeature( f ) )
      {
        remoteFeatureIds << f.id();

        // NOTE: Spatialite provider ignores position of geometry column
        // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
        int column = 0;
        QgsAttributes attrs = f.attributes();
        QgsAttributes newAttrs( attrs.count() );
        for ( int it = 0; it < attrs.count(); ++it )
        {
          newAttrs[column++] = attrs[it];
        }
        f.setAttributes( newAttrs );

        newLayer->addFeature( f, false );

        emit progressUpdated( featureCount++ );
      }
      if ( newLayer->commitChanges() )
      {
        emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->featureCount() );
        featureCount = 1;

        // update feature id lookup
        int layerId = getOrCreateLayerId( db, newLayer->id() );
        QList<QgsFeatureId> offlineFeatureIds;

        QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
        while ( fit.nextFeature( f ) )
        {
          offlineFeatureIds << f.id();
        }

        // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
        sqlExec( db, "BEGIN" );
        int remoteCount = remoteFeatureIds.size();
        for ( int i = 0; i < remoteCount; i++ )
        {
          addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) );
          emit progressUpdated( featureCount++ );
        }
        sqlExec( db, "COMMIT" );
      }
      else
      {
        showWarning( newLayer->commitErrors().join( "\n" ) );
      }

      // remove remote layer
      QgsMapLayerRegistry::instance()->removeMapLayers(
        QStringList() << layer->id() );
    }
  }
}
Example #2
0
  QDomDocument createTransactionDocument( QgsServerInterface* serverIface, const QString& version,
                                          const QgsServerRequest& request )
  {
    Q_UNUSED( version );

    QDomDocument doc;

    QgsWfsProjectParser* configParser = getConfigParser( serverIface );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
    QgsAccessControl* accessControl = serverIface->accessControls();
#endif
    const QString requestBody = request.getParameter( QStringLiteral( "REQUEST_BODY" ) );

    QString errorMsg;
    if ( !doc.setContent( requestBody, true, &errorMsg ) )
    {
      throw QgsRequestNotWellFormedException( errorMsg );
    }

    QDomElement docElem = doc.documentElement();
    QDomNodeList docChildNodes = docElem.childNodes();

    // Re-organize the transaction document
    QDomDocument mDoc;
    QDomElement mDocElem = mDoc.createElement( QStringLiteral( "myTransactionDocument" ) );
    mDocElem.setAttribute( QStringLiteral( "xmlns" ), QGS_NAMESPACE );
    mDocElem.setAttribute( QStringLiteral( "xmlns:wfs" ), WFS_NAMESPACE );
    mDocElem.setAttribute( QStringLiteral( "xmlns:gml" ), GML_NAMESPACE );
    mDocElem.setAttribute( QStringLiteral( "xmlns:ogc" ), OGC_NAMESPACE );
    mDocElem.setAttribute( QStringLiteral( "xmlns:qgs" ), QGS_NAMESPACE );
    mDocElem.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
    mDoc.appendChild( mDocElem );

    QDomElement actionElem;
    QString actionName;
    QDomElement typeNameElem;
    QString typeName;

    for ( int i = docChildNodes.count(); 0 < i; --i )
    {
      actionElem = docChildNodes.at( i - 1 ).toElement();
      actionName = actionElem.localName();

      if ( actionName == QLatin1String( "Insert" ) )
      {
        QDomElement featureElem = actionElem.firstChild().toElement();
        typeName = featureElem.localName();
      }
      else if ( actionName == QLatin1String( "Update" ) )
      {
        typeName = actionElem.attribute( QStringLiteral( "typeName" ) );
      }
      else if ( actionName == QLatin1String( "Delete" ) )
      {
        typeName = actionElem.attribute( QStringLiteral( "typeName" ) );
      }

      if ( typeName.contains( QLatin1String( ":" ) ) )
        typeName = typeName.section( QStringLiteral( ":" ), 1, 1 );

      QDomNodeList typeNameList = mDocElem.elementsByTagName( typeName );
      if ( typeNameList.count() == 0 )
      {
        typeNameElem = mDoc.createElement( typeName );
        mDocElem.appendChild( typeNameElem );
      }
      else
        typeNameElem = typeNameList.at( 0 ).toElement();

      typeNameElem.appendChild( actionElem );
    }

    // It's time to make the transaction
    // Create the response document
    QDomDocument resp;
    //wfs:WFS_TransactionRespone element
    QDomElement respElem = resp.createElement( QStringLiteral( "WFS_TransactionResponse" )/*wfs:WFS_TransactionResponse*/ );
    respElem.setAttribute( QStringLiteral( "xmlns" ), WFS_NAMESPACE );
    respElem.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
    respElem.setAttribute( QStringLiteral( "xsi:schemaLocation" ), WFS_NAMESPACE + " http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
    respElem.setAttribute( QStringLiteral( "xmlns:ogc" ), OGC_NAMESPACE );
    respElem.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) );
    resp.appendChild( respElem );

    // Store the created feature id for WFS
    QStringList insertResults;
    // Get the WFS layers id
    QStringList wfsLayersId = configParser->wfsLayers();;

    QList<QgsMapLayer*> layerList;
    QgsMapLayer* currentLayer = nullptr;

    // Loop through the layer transaction elements
    docChildNodes = mDocElem.childNodes();
    for ( int i = 0; i < docChildNodes.count(); ++i )
    {
      // Get the vector layer
      typeNameElem = docChildNodes.at( i ).toElement();
      typeName = typeNameElem.tagName();

      layerList = configParser->mapLayerFromTypeName( typeName );
      // Could be empty!
      if ( layerList.count() > 0 )
      {
        currentLayer = layerList.at( 0 );
      }
      else
      {
        throw QgsRequestNotWellFormedException( QStringLiteral( "Wrong TypeName: %1" ).arg( typeName ) );
      }

      QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>( currentLayer );
      // it's a vectorlayer and defined by the administrator as a WFS layer
      if ( layer && wfsLayersId.contains( layer->id() ) )
      {
#ifdef HAVE_SERVER_PYTHON_PLUGINS
        if ( actionName == QLatin1String( "Insert" ) )
        {
          if ( !accessControl->layerInsertPermission( layer ) )
          {
            throw QgsSecurityAccessException( QStringLiteral( "Feature insert permission denied" ) );
          }
        }
        else if ( actionName == QLatin1String( "Update" ) )
        {
          if ( !accessControl->layerUpdatePermission( layer ) )
          {
            throw QgsSecurityAccessException( QStringLiteral( "Feature update permission denied" ) );
          }
        }
        else if ( actionName == QLatin1String( "Delete" ) )
        {
          if ( !accessControl->layerDeletePermission( layer ) )
          {
            throw QgsSecurityAccessException( QStringLiteral( "Feature delete permission denied" ) );
          }
        }
#endif

        // Get the provider and it's capabilities
        QgsVectorDataProvider* provider = layer->dataProvider();
        if ( !provider )
        {
          continue;
        }

        int cap = provider->capabilities();

        // Start the update transaction
        layer->startEditing();
        if (( cap & QgsVectorDataProvider::ChangeAttributeValues ) && ( cap & QgsVectorDataProvider::ChangeGeometries ) )
        {
          // Loop through the update elements for this layer
          QDomNodeList upNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Update" ) );
          for ( int j = 0; j < upNodeList.count(); ++j )
          {
            if ( !configParser->wfstUpdateLayers().contains( layer->id() ) )
            {
              //no wfs permissions to do updates
              QString errorMsg = "No permissions to do WFS updates on layer '" + layer->name() + "'";
              QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL );
              addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Update" ), errorMsg );
              return resp;
            }

            actionElem = upNodeList.at( j ).toElement();

            // Get the Feature Ids for this filter on the layer
            QDomElement filterElem = actionElem.elementsByTagName( QStringLiteral( "Filter" ) ).at( 0 ).toElement();
            QgsFeatureIds fids = getFeatureIdsFromFilter( filterElem, layer );

            // Loop through the property elements
            // Store properties and the geometry element
            QDomNodeList propertyNodeList = actionElem.elementsByTagName( QStringLiteral( "Property" ) );
            QMap<QString, QString> propertyMap;
            QDomElement propertyElem;
            QDomElement nameElem;
            QDomElement valueElem;
            QDomElement geometryElem;

            for ( int l = 0; l < propertyNodeList.count(); ++l )
            {
              propertyElem = propertyNodeList.at( l ).toElement();
              nameElem = propertyElem.elementsByTagName( QStringLiteral( "Name" ) ).at( 0 ).toElement();
              valueElem = propertyElem.elementsByTagName( QStringLiteral( "Value" ) ).at( 0 ).toElement();
              if ( nameElem.text() != QLatin1String( "geometry" ) )
              {
                propertyMap.insert( nameElem.text(), valueElem.text() );
              }
              else
              {
                geometryElem = valueElem;
              }
            }

            // Update the features
            QgsFields fields = provider->fields();
            QMap<QString, int> fieldMap = provider->fieldNameMap();
            QMap<QString, int>::const_iterator fieldMapIt;
            QString fieldName;
            bool conversionSuccess;

            QgsFeatureIds::const_iterator fidIt = fids.constBegin();
            for ( ; fidIt != fids.constEnd(); ++fidIt )
            {
#ifdef HAVE_SERVER_PYTHON_PLUGINS
              QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) );
              QgsFeature feature;
              while ( fit.nextFeature( feature ) )
              {
                if ( !accessControl->allowToEdit( layer, feature ) )
                {
                  throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) );
                }
              }
#endif

              QMap< QString, QString >::const_iterator it = propertyMap.constBegin();
              for ( ; it != propertyMap.constEnd(); ++it )
              {
                fieldName = it.key();
                fieldMapIt = fieldMap.find( fieldName );
                if ( fieldMapIt == fieldMap.constEnd() )
                {
                  continue;
                }
                QgsField field = fields.at( fieldMapIt.value() );
                if ( field.type() == 2 )
                  layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value().toInt( &conversionSuccess ) );
                else if ( field.type() == 6 )
                  layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value().toDouble( &conversionSuccess ) );
                else
                  layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value() );
              }

              if ( !geometryElem.isNull() )
              {
                QgsGeometry g = QgsOgcUtils::geometryFromGML( geometryElem );
                if ( !layer->changeGeometry( *fidIt, g ) )
                {
                  throw QgsRequestNotWellFormedException( QStringLiteral( "Error in change geometry" ) );
                }
              }

#ifdef HAVE_SERVER_PYTHON_PLUGINS
              fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) );
              while ( fit.nextFeature( feature ) )
              {
                if ( !accessControl->allowToEdit( layer, feature ) )
                {
                  layer->rollBack();
                  throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) );
                }
              }
#endif
            }
          }
        }
        // Commit the changes of the update elements
        if ( !layer->commitChanges() )
        {
          addTransactionResult( resp, respElem, QStringLiteral( "PARTIAL" ), QStringLiteral( "Update" ), layer->commitErrors().join( QStringLiteral( "\n  " ) ) );
          return resp;
        }
        // Start the delete transaction
        layer->startEditing();
        if (( cap & QgsVectorDataProvider::DeleteFeatures ) )
        {
          // Loop through the delete elements
          QDomNodeList delNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Delete" ) );
          for ( int j = 0; j < delNodeList.count(); ++j )
          {
            if ( !configParser->wfstDeleteLayers().contains( layer->id() ) )
            {
              //no wfs permissions to do updates
              QString errorMsg = "No permissions to do WFS deletes on layer '" + layer->name() + "'";
              QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL );
              addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Delete" ), errorMsg );
              return resp;
            }

            actionElem = delNodeList.at( j ).toElement();
            QDomElement filterElem = actionElem.firstChild().toElement();
            // Get Feature Ids for the Filter element
            QgsFeatureIds fids = getFeatureIdsFromFilter( filterElem, layer );

#ifdef HAVE_SERVER_PYTHON_PLUGINS
            QgsFeatureIds::const_iterator fidIt = fids.constBegin();
            for ( ; fidIt != fids.constEnd(); ++fidIt )
            {
              QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) );
              QgsFeature feature;
              while ( fit.nextFeature( feature ) )
              {
                if ( !accessControl->allowToEdit( layer, feature ) )
                {
                  throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) );
                }
              }
            }
#endif

            layer->selectByIds( fids );
            layer->deleteSelectedFeatures();
          }
        }
        // Commit the changes of the delete elements
        if ( !layer->commitChanges() )
        {
          addTransactionResult( resp, respElem, QStringLiteral( "PARTIAL" ), QStringLiteral( "Delete" ), layer->commitErrors().join( QStringLiteral( "\n  " ) ) );
          return resp;
        }

        // Store the inserted features
        QgsFeatureList inFeatList;
        if ( cap & QgsVectorDataProvider::AddFeatures )
        {
          // Get Layer Field Information
          QgsFields fields = provider->fields();
          QMap<QString, int> fieldMap = provider->fieldNameMap();
          QMap<QString, int>::const_iterator fieldMapIt;

          // Loop through the insert elements
          QDomNodeList inNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Insert" ) );
          for ( int j = 0; j < inNodeList.count(); ++j )
          {
            if ( !configParser->wfstInsertLayers().contains( layer->id() ) )
            {
              //no wfs permissions to do updates
              QString errorMsg = "No permissions to do WFS inserts on layer '" + layer->name() + "'";
              QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL );
              addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Insert" ), errorMsg );
              return resp;
            }

            actionElem = inNodeList.at( j ).toElement();
            // Loop through the feature element
            QDomNodeList featNodes = actionElem.childNodes();
            for ( int l = 0; l < featNodes.count(); l++ )
            {
              // Add the feature to the layer
              // and store it to put it's Feature Id in the response
              inFeatList << QgsFeature( fields );

              // Create feature for this layer
              QDomElement featureElem = featNodes.at( l ).toElement();

              QDomNode currentAttributeChild = featureElem.firstChild();

              while ( !currentAttributeChild.isNull() )
              {
                QDomElement currentAttributeElement = currentAttributeChild.toElement();
                QString attrName = currentAttributeElement.localName();

                if ( attrName != QLatin1String( "boundedBy" ) )
                {
                  if ( attrName != QLatin1String( "geometry" ) ) //a normal attribute
                  {
                    fieldMapIt = fieldMap.find( attrName );
                    if ( fieldMapIt == fieldMap.constEnd() )
                    {
                      continue;
                    }
                    QgsField field = fields.at( fieldMapIt.value() );
                    QString attrValue = currentAttributeElement.text();
                    int attrType = field.type();
                    QgsMessageLog::logMessage( QStringLiteral( "attr: name=%1 idx=%2 value=%3" ).arg( attrName ).arg( fieldMapIt.value() ).arg( attrValue ) );
                    if ( attrType == QVariant::Int )
                      inFeatList.last().setAttribute( fieldMapIt.value(), attrValue.toInt() );
                    else if ( attrType == QVariant::Double )
                      inFeatList.last().setAttribute( fieldMapIt.value(), attrValue.toDouble() );
                    else
                      inFeatList.last().setAttribute( fieldMapIt.value(), attrValue );
                  }
                  else //a geometry attribute
                  {
                    QgsGeometry g = QgsOgcUtils::geometryFromGML( currentAttributeElement );
                    inFeatList.last().setGeometry( g );
                  }
                }
                currentAttributeChild = currentAttributeChild.nextSibling();
              }
            }
          }
        }
#ifdef HAVE_SERVER_PYTHON_PLUGINS
        QgsFeatureList::iterator featureIt = inFeatList.begin();
        while ( featureIt != inFeatList.end() )
        {
          if ( !accessControl->allowToEdit( layer, *featureIt ) )
          {
            throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) );
          }
          featureIt++;
        }
#endif

        // add the features
        if ( !provider->addFeatures( inFeatList ) )
        {
          addTransactionResult( resp, respElem, QStringLiteral( "Partial" ), QStringLiteral( "Insert" ), layer->commitErrors().join( QStringLiteral( "\n  " ) ) );
          if ( provider->hasErrors() )
          {
            provider->clearErrors();
          }
          return resp;
        }
        // Get the Feature Ids of the inserted feature
        for ( int j = 0; j < inFeatList.size(); j++ )
        {
          insertResults << typeName + "." + QString::number( inFeatList[j].id() );
        }
      }
    }

    // Put the Feature Ids of the inserted feature
    if ( !insertResults.isEmpty() )
    {
      Q_FOREACH ( const QString &fidStr, insertResults )
      {
        QDomElement irElem = doc.createElement( QStringLiteral( "InsertResult" ) );
        QDomElement fiElem = doc.createElement( QStringLiteral( "ogc:FeatureId" ) );
        fiElem.setAttribute( QStringLiteral( "fid" ), fidStr );
        irElem.appendChild( fiElem );
        respElem.appendChild( irElem );
      }
Example #3
0
void QgsOfflineEditing::synchronize()
{
  // open logging db
  sqlite3* db = openLoggingDb();
  if ( db == NULL )
  {
    return;
  }

  emit progressStarted();

  // restore and sync remote layers
  QList<QgsMapLayer*> offlineLayers;
  QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
  for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
  {
    QgsMapLayer* layer = layer_it.value();
    if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
    {
      offlineLayers << layer;
    }
  }

  for ( int l = 0; l < offlineLayers.count(); l++ )
  {
    QgsMapLayer* layer = offlineLayers[l];

    emit layerProgressUpdated( l + 1, offlineLayers.count() );

    QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
    QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
    QString remoteName = layer->name();
    remoteName.remove( QRegExp( " \\(offline\\)$" ) );

    QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
    if ( remoteLayer->isValid() )
    {
      // TODO: only add remote layer if there are log entries?

      QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );

      // copy style
      copySymbology( offlineLayer, remoteLayer );

      // register this layer with the central layers registry
      QgsMapLayerRegistry::instance()->addMapLayers(
        QList<QgsMapLayer *>() << remoteLayer, true );

      // apply layer edit log
      QString qgisLayerId = layer->id();
      QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
      int layerId = sqlQueryInt( db, sql, -1 );
      if ( layerId != -1 )
      {
        remoteLayer->startEditing();

        // TODO: only get commitNos of this layer?
        int commitNo = getCommitNo( db );
        for ( int i = 0; i < commitNo; i++ )
        {
          // apply commits chronologically
          applyAttributesAdded( remoteLayer, db, layerId, i );
          applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
          applyGeometryChanges( remoteLayer, db, layerId, i );
        }

        applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
        applyFeaturesRemoved( remoteLayer, db, layerId );

        if ( remoteLayer->commitChanges() )
        {
          // update fid lookup
          updateFidLookup( remoteLayer, db, layerId );

          // clear edit log for this layer
          sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
          sqlExec( db, sql );
          sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
          sqlExec( db, sql );
          sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
          sqlExec( db, sql );
          sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
          sqlExec( db, sql );
          sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
          sqlExec( db, sql );

          // reset commitNo
          QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
          sqlExec( db, sql );
        }
        else
        {
          showWarning( remoteLayer->commitErrors().join( "\n" ) );
        }
      }

      // remove offline layer
      QgsMapLayerRegistry::instance()->removeMapLayers(
        ( QStringList() << qgisLayerId ) );

      // disable offline project
      QString projectTitle = QgsProject::instance()->title();
      projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
      QgsProject::instance()->title( projectTitle );
      QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
      remoteLayer->reload(); //update with other changes
    }
  }

  emit progressStopped();

  sqlite3_close( db );
}