Skip to content

Commit

Permalink
Merge pull request #4134 from nyalldawson/save_task
Browse files Browse the repository at this point in the history
[FEATURE] Background saving of vector layers
  • Loading branch information
nyalldawson committed Feb 14, 2017
2 parents bd46b78 + 35bb5c6 commit edb30a2
Show file tree
Hide file tree
Showing 18 changed files with 497 additions and 91 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -149,6 +149,7 @@
%Include qgsunittypes.sip
%Include qgsvectordataprovider.sip
%Include qgsvectorfilewriter.sip
%Include qgsvectorfilewritertask.sip
%Include qgsvectorlayer.sip
%Include qgsvectorlayercache.sip
%Include qgsvectorlayereditbuffer.sip
Expand Down
7 changes: 7 additions & 0 deletions python/core/qgsfeedback.sip
Expand Up @@ -36,8 +36,15 @@ class QgsFeedback : QObject
//! Tells whether the operation has been canceled already
bool isCanceled() const;

void setProgress( double progress );

double progress() const;

signals:
//! Internal routines can connect to this signal if they use event loop
void canceled();

void progressChanged( double progress );


};
37 changes: 5 additions & 32 deletions python/core/qgstaskmanager.sip
Expand Up @@ -78,14 +78,7 @@ class QgsTask : QObject
*/
double progress() const;

/**
* Notifies the task that it should terminate. Calling this is not guaranteed
* to immediately end the task, rather it sets the isCanceled() flag which
* task subclasses can check and terminate their operations at an appropriate
* time. Any subtasks owned by this task will also be canceled.
* @see isCanceled()
*/
void cancel();
virtual void cancel();

/**
* Places the task on hold. If the task in not queued
Expand Down Expand Up @@ -133,19 +126,9 @@ class QgsTask : QObject
void addSubTask( QgsTask* subTask /Transfer/, const QgsTaskList& dependencies = QgsTaskList(),
SubTaskDependency subTaskDependency = SubTaskIndependent );

/**
* Sets a list of layer IDs on which the task depends. The task will automatically
* be canceled if any of these layers are about to be removed.
* @see dependentLayerIds()
*/
void setDependentLayers( const QStringList& dependentLayerIds );
void setDependentLayers( const QList<QgsMapLayer*>& dependentLayers );

/**
* Returns the list of layer IDs on which the task depends. The task will automatically
* be canceled if any of these layers are about to be removed.
* @see setDependentLayers()
*/
QStringList dependentLayerIds() const;
QList< QgsMapLayer* > dependentLayers() const;

signals:

Expand Down Expand Up @@ -324,19 +307,9 @@ class QgsTaskManager : QObject
//! @note not available in Python bindings
//QSet< long > dependencies( long taskId ) const;

/** Returns a list of layers on which as task is dependent. The task will automatically
* be canceled if any of these layers are above to be removed.
* @param taskId task ID
* @returns list of layer IDs
* @see tasksDependentOnLayer()
*/
QStringList dependentLayers( long taskId ) const;
QList< QgsMapLayer* > dependentLayers( long taskId ) const;

/**
* Returns a list of tasks which depend on a layer.
* @see dependentLayers()
*/
QList< QgsTask* > tasksDependentOnLayer( const QString& layerId ) const;
QList< QgsTask* > tasksDependentOnLayer( QgsMapLayer* layer ) const;

/** Returns a list of the active (queued or running) tasks.
* @see countActiveTasks()
Expand Down
3 changes: 3 additions & 0 deletions python/core/qgsvectorfilewriter.sip
Expand Up @@ -97,6 +97,7 @@ class QgsVectorFileWriter
ErrProjection,
ErrFeatureWriteFailed,
ErrInvalidLayer,
Canceled,
};

enum SymbologyExport
Expand Down Expand Up @@ -323,6 +324,8 @@ class QgsVectorFileWriter

/** Field value converter */
QgsVectorFileWriter::FieldValueConverter* fieldValueConverter;

QgsFeedback* feedback;
};

/** Writes a layer out to a vector file.
Expand Down
25 changes: 25 additions & 0 deletions python/core/qgsvectorfilewritertask.sip
@@ -0,0 +1,25 @@
class QgsVectorFileWriterTask : QgsTask
{
%TypeHeaderCode
#include <qgsvectorfilewritertask.h>
%End

public:

QgsVectorFileWriterTask( QgsVectorLayer* layer,
const QString& fileName,
const QgsVectorFileWriter::SaveVectorOptions& options );

virtual void cancel();

signals:

void writeComplete( const QString& newFilename );
void errorOccurred( int error, const QString& errorMessage );

protected:

virtual bool run();
virtual void finished( bool result );
};

56 changes: 30 additions & 26 deletions src/app/qgisapp.cpp
Expand Up @@ -250,6 +250,7 @@
#include "qgsvectorlayerjoininfo.h"
#include "qgsvectorlayerutils.h"
#include "qgshelp.h"
#include "qgsvectorfilewritertask.h"

#include "qgssublayersdialog.h"
#include "ogr/qgsopenvectorlayerdialog.h"
Expand Down Expand Up @@ -6477,12 +6478,6 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
}
}

// ok if the file existed it should be deleted now so we can continue...
QApplication::setOverrideCursor( Qt::WaitCursor );

QgsVectorFileWriter::WriterError error;
QString errorMessage;
QString newFilename;
QgsRectangle filterExtent = dialog->filterExtent();
QgisAppFieldValueConverter converter( vlayer, dialog->attributesAsDisplayedValues() );
QgisAppFieldValueConverter* converterPtr = nullptr;
Expand Down Expand Up @@ -6510,32 +6505,41 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
options.attributes = dialog->selectedAttributes();
options.fieldValueConverter = converterPtr;

error = QgsVectorFileWriter::writeAsVectorFormat(
vlayer, vectorFilename, options, &newFilename, &errorMessage );
bool addToCanvas = dialog->addToCanvas();
QString layerName = dialog->layername();
QgsVectorFileWriterTask* writerTask = new QgsVectorFileWriterTask( vlayer, vectorFilename, options );

QApplication::restoreOverrideCursor();

if ( error == QgsVectorFileWriter::NoError )
// when writer is successful:
connect( writerTask, &QgsVectorFileWriterTask::writeComplete, this, [this, addToCanvas, layerName, encoding, vectorFilename, vlayer]( const QString& newFilename )
{
if ( dialog->addToCanvas() )
if ( addToCanvas )
{
QString uri( newFilename );
if ( !dialog->layername().isEmpty() )
uri += "|layername=" + dialog->layername();
addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
if ( !layerName.isEmpty() )
uri += "|layername=" + layerName;
this->addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
}
emit layerSavedAs( vlayer, vectorFilename );
messageBar()->pushMessage( tr( "Saving done" ),
tr( "Export to vector file has been completed" ),
QgsMessageBar::INFO, messageTimeout() );
this->emit layerSavedAs( vlayer, vectorFilename );
this->messageBar()->pushMessage( tr( "Saving done" ),
tr( "Export to vector file has been completed" ),
QgsMessageBar::INFO, messageTimeout() );
}
else
);

// when an error occurs:
connect( writerTask, &QgsVectorFileWriterTask::errorOccurred, this, [=]( int error, const QString & errorMessage )
{
QgsMessageViewer *m = new QgsMessageViewer( nullptr );
m->setWindowTitle( tr( "Save error" ) );
m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
m->exec();
if ( error != QgsVectorFileWriter::Canceled )
{
QgsMessageViewer *m = new QgsMessageViewer( nullptr );
m->setWindowTitle( tr( "Save error" ) );
m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
m->exec();
}
}
);

QgsApplication::taskManager()->addTask( writerTask );
}

delete dialog;
Expand Down Expand Up @@ -8440,7 +8444,7 @@ void QgisApp::removeLayer()
QStringList activeTaskDescriptions;
Q_FOREACH ( QgsMapLayer * layer, mLayerTreeView->selectedLayers() )
{
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer->id() );
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer );
if ( !tasks.isEmpty() )
{
Q_FOREACH ( QgsTask* task, tasks )
Expand Down Expand Up @@ -9645,7 +9649,7 @@ bool QgisApp::checkTasksDependOnProject()

for ( ; layerIt != layers.constEnd(); ++layerIt )
{
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.key() );
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.value() );
if ( !tasks.isEmpty() )
{
Q_FOREACH ( QgsTask* task, tasks )
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -237,6 +237,7 @@ SET(QGIS_CORE_SRCS
qgsunittypes.cpp
qgsvectordataprovider.cpp
qgsvectorfilewriter.cpp
qgsvectorfilewritertask.cpp
qgsvectorlayer.cpp
qgsvectorlayercache.cpp
qgsvectorlayerdiagramprovider.cpp
Expand Down Expand Up @@ -528,6 +529,7 @@ SET(QGIS_CORE_MOC_HDRS
qgstransactiongroup.h
qgsunittypes.h
qgsvectordataprovider.h
qgsvectorfilewritertask.h
qgsvectorlayercache.h
qgsvectorlayereditbuffer.h
qgsvectorlayereditpassthrough.h
Expand Down
31 changes: 31 additions & 0 deletions src/core/qgsfeedback.h
Expand Up @@ -61,13 +61,44 @@ class CORE_EXPORT QgsFeedback : public QObject
//! Tells whether the operation has been canceled already
bool isCanceled() const { return mCanceled; }

/**
* Sets the current progress for the feedback object. The \a progress
* argument is in percentage and valid values range from 0-100.
* @see progress()
* @see progressChanged()
* @note added in QGIS 3.0
*/
void setProgress( double progress ) { mProgress = progress; emit progressChanged( mProgress ); }

/**
* Returns the current progress reported by the feedback object. Depending on how the
* feedback object is used progress reporting may not be supported. The returned value
* is in percentage and ranges from 0-100.
* @see setProgress()
* @see progressChanged()
* @note added in QGIS 3.0
*/
double progress() const { return mProgress; }

signals:
//! Internal routines can connect to this signal if they use event loop
void canceled();

/**
* Emitted when the feedback object reports a progress change. Depending on how the
* feedback object is used progress reporting may not be supported. The \a progress
* argument is in percentage and ranges from 0-100.
* @note added in QGIS 3.0
* @see setProgress()
* @see progress()
*/
void progressChanged( double progress );

private:
//! Whether the operation has been canceled already. False by default.
bool mCanceled;

double mProgress = 0.0;
};

#endif // QGSFEEDBACK_H
38 changes: 22 additions & 16 deletions src/core/qgstaskmanager.cpp
Expand Up @@ -17,6 +17,7 @@

#include "qgstaskmanager.h"
#include "qgsproject.h"
#include "qgsmaplayerlistutils.h"
#include <QtConcurrentRun>


Expand Down Expand Up @@ -136,9 +137,14 @@ void QgsTask::addSubTask( QgsTask* subTask, const QgsTaskList& dependencies,
connect( subTask, &QgsTask::statusChanged, this, &QgsTask::subTaskStatusChanged );
}

void QgsTask::setDependentLayers( const QStringList& dependentLayerIds )
QList<QgsMapLayer*> QgsTask::dependentLayers() const
{
mDependentLayerIds = dependentLayerIds;
return _qgis_listQPointerToRaw( mDependentLayers );
}

void QgsTask::setDependentLayers( const QList< QgsMapLayer* >& dependentLayers )
{
mDependentLayers = _qgis_listRawToQPointer( dependentLayers );
}

void QgsTask::subTaskStatusChanged( int status )
Expand Down Expand Up @@ -325,8 +331,8 @@ QgsTaskManager::QgsTaskManager( QObject* parent )
, mTaskMutex( new QMutex( QMutex::Recursive ) )
, mNextTaskId( 0 )
{
connect( QgsProject::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ),
this, SLOT( layersWillBeRemoved( QStringList ) ) );
connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer* >& ) > ( &QgsProject::layersWillBeRemoved ),
this, &QgsTaskManager::layersWillBeRemoved );
}

QgsTaskManager::~QgsTaskManager()
Expand Down Expand Up @@ -376,8 +382,8 @@ long QgsTaskManager::addTaskPrivate( QgsTask* task, QgsTaskList dependencies, bo
{
mParentTasks << task;
}
if ( !task->dependentLayerIds().isEmpty() )
mLayerDependencies.insert( taskId, task->dependentLayerIds() );
if ( !task->dependentLayers().isEmpty() )
mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
mTaskMutex->unlock();

connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
Expand Down Expand Up @@ -546,20 +552,20 @@ bool QgsTaskManager::hasCircularDependencies( long taskId ) const
return !resolveDependencies( taskId, taskId, d );
}

QStringList QgsTaskManager::dependentLayers( long taskId ) const
QList<QgsMapLayer*> QgsTaskManager::dependentLayers( long taskId ) const
{
QMutexLocker ml( mTaskMutex );
return mLayerDependencies.value( taskId, QStringList() );
return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
}

QList<QgsTask*> QgsTaskManager::tasksDependentOnLayer( const QString& layerId ) const
QList<QgsTask*> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer* layer ) const
{
QMutexLocker ml( mTaskMutex );
QList< QgsTask* > tasks;
QMap< long, QStringList >::const_iterator layerIt = mLayerDependencies.constBegin();
QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
{
if ( layerIt.value().contains( layerId ) )
if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
{
QgsTask* layerTask = task( layerIt.key() );
if ( layerTask )
Expand Down Expand Up @@ -648,21 +654,21 @@ void QgsTaskManager::taskStatusChanged( int status )

}

void QgsTaskManager::layersWillBeRemoved( const QStringList& layerIds )
void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer* >& layers )
{
mTaskMutex->lock();
// scan through layers to be removed
QMap< long, QStringList > layerDependencies = mLayerDependencies;
QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
layerDependencies.detach();
mTaskMutex->unlock();

Q_FOREACH ( const QString& layerId, layerIds )
Q_FOREACH ( QgsMapLayer* layer, layers )
{
// scan through tasks with layer dependencies
for ( QMap< long, QStringList >::const_iterator it = layerDependencies.constBegin();
for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
it != layerDependencies.constEnd(); ++it )
{
if ( !it.value().contains( layerId ) )
if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
{
//task not dependent on this layer
continue;
Expand Down

0 comments on commit edb30a2

Please sign in to comment.