Skip to content

Commit edb30a2

Browse files
authoredFeb 14, 2017
Merge pull request #4134 from nyalldawson/save_task
[FEATURE] Background saving of vector layers
2 parents bd46b78 + 35bb5c6 commit edb30a2

18 files changed

+497
-91
lines changed
 

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
%Include qgsunittypes.sip
150150
%Include qgsvectordataprovider.sip
151151
%Include qgsvectorfilewriter.sip
152+
%Include qgsvectorfilewritertask.sip
152153
%Include qgsvectorlayer.sip
153154
%Include qgsvectorlayercache.sip
154155
%Include qgsvectorlayereditbuffer.sip

‎python/core/qgsfeedback.sip

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,15 @@ class QgsFeedback : QObject
3636
//! Tells whether the operation has been canceled already
3737
bool isCanceled() const;
3838

39+
void setProgress( double progress );
40+
41+
double progress() const;
42+
3943
signals:
4044
//! Internal routines can connect to this signal if they use event loop
4145
void canceled();
4246

47+
void progressChanged( double progress );
48+
49+
4350
};

‎python/core/qgstaskmanager.sip

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,7 @@ class QgsTask : QObject
7878
*/
7979
double progress() const;
8080

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

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

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

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

150133
signals:
151134

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

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

335-
/**
336-
* Returns a list of tasks which depend on a layer.
337-
* @see dependentLayers()
338-
*/
339-
QList< QgsTask* > tasksDependentOnLayer( const QString& layerId ) const;
312+
QList< QgsTask* > tasksDependentOnLayer( QgsMapLayer* layer ) const;
340313

341314
/** Returns a list of the active (queued or running) tasks.
342315
* @see countActiveTasks()

‎python/core/qgsvectorfilewriter.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class QgsVectorFileWriter
9797
ErrProjection,
9898
ErrFeatureWriteFailed,
9999
ErrInvalidLayer,
100+
Canceled,
100101
};
101102

102103
enum SymbologyExport
@@ -323,6 +324,8 @@ class QgsVectorFileWriter
323324

324325
/** Field value converter */
325326
QgsVectorFileWriter::FieldValueConverter* fieldValueConverter;
327+
328+
QgsFeedback* feedback;
326329
};
327330

328331
/** Writes a layer out to a vector file.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class QgsVectorFileWriterTask : QgsTask
2+
{
3+
%TypeHeaderCode
4+
#include <qgsvectorfilewritertask.h>
5+
%End
6+
7+
public:
8+
9+
QgsVectorFileWriterTask( QgsVectorLayer* layer,
10+
const QString& fileName,
11+
const QgsVectorFileWriter::SaveVectorOptions& options );
12+
13+
virtual void cancel();
14+
15+
signals:
16+
17+
void writeComplete( const QString& newFilename );
18+
void errorOccurred( int error, const QString& errorMessage );
19+
20+
protected:
21+
22+
virtual bool run();
23+
virtual void finished( bool result );
24+
};
25+

‎src/app/qgisapp.cpp

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@
250250
#include "qgsvectorlayerjoininfo.h"
251251
#include "qgsvectorlayerutils.h"
252252
#include "qgshelp.h"
253+
#include "qgsvectorfilewritertask.h"
253254

254255
#include "qgssublayersdialog.h"
255256
#include "ogr/qgsopenvectorlayerdialog.h"
@@ -6477,12 +6478,6 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
64776478
}
64786479
}
64796480

6480-
// ok if the file existed it should be deleted now so we can continue...
6481-
QApplication::setOverrideCursor( Qt::WaitCursor );
6482-
6483-
QgsVectorFileWriter::WriterError error;
6484-
QString errorMessage;
6485-
QString newFilename;
64866481
QgsRectangle filterExtent = dialog->filterExtent();
64876482
QgisAppFieldValueConverter converter( vlayer, dialog->attributesAsDisplayedValues() );
64886483
QgisAppFieldValueConverter* converterPtr = nullptr;
@@ -6510,32 +6505,41 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
65106505
options.attributes = dialog->selectedAttributes();
65116506
options.fieldValueConverter = converterPtr;
65126507

6513-
error = QgsVectorFileWriter::writeAsVectorFormat(
6514-
vlayer, vectorFilename, options, &newFilename, &errorMessage );
6508+
bool addToCanvas = dialog->addToCanvas();
6509+
QString layerName = dialog->layername();
6510+
QgsVectorFileWriterTask* writerTask = new QgsVectorFileWriterTask( vlayer, vectorFilename, options );
65156511

6516-
QApplication::restoreOverrideCursor();
6517-
6518-
if ( error == QgsVectorFileWriter::NoError )
6512+
// when writer is successful:
6513+
connect( writerTask, &QgsVectorFileWriterTask::writeComplete, this, [this, addToCanvas, layerName, encoding, vectorFilename, vlayer]( const QString& newFilename )
65196514
{
6520-
if ( dialog->addToCanvas() )
6515+
if ( addToCanvas )
65216516
{
65226517
QString uri( newFilename );
6523-
if ( !dialog->layername().isEmpty() )
6524-
uri += "|layername=" + dialog->layername();
6525-
addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
6518+
if ( !layerName.isEmpty() )
6519+
uri += "|layername=" + layerName;
6520+
this->addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
65266521
}
6527-
emit layerSavedAs( vlayer, vectorFilename );
6528-
messageBar()->pushMessage( tr( "Saving done" ),
6529-
tr( "Export to vector file has been completed" ),
6530-
QgsMessageBar::INFO, messageTimeout() );
6522+
this->emit layerSavedAs( vlayer, vectorFilename );
6523+
this->messageBar()->pushMessage( tr( "Saving done" ),
6524+
tr( "Export to vector file has been completed" ),
6525+
QgsMessageBar::INFO, messageTimeout() );
65316526
}
6532-
else
6527+
);
6528+
6529+
// when an error occurs:
6530+
connect( writerTask, &QgsVectorFileWriterTask::errorOccurred, this, [=]( int error, const QString & errorMessage )
65336531
{
6534-
QgsMessageViewer *m = new QgsMessageViewer( nullptr );
6535-
m->setWindowTitle( tr( "Save error" ) );
6536-
m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
6537-
m->exec();
6532+
if ( error != QgsVectorFileWriter::Canceled )
6533+
{
6534+
QgsMessageViewer *m = new QgsMessageViewer( nullptr );
6535+
m->setWindowTitle( tr( "Save error" ) );
6536+
m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
6537+
m->exec();
6538+
}
65386539
}
6540+
);
6541+
6542+
QgsApplication::taskManager()->addTask( writerTask );
65396543
}
65406544

65416545
delete dialog;
@@ -8440,7 +8444,7 @@ void QgisApp::removeLayer()
84408444
QStringList activeTaskDescriptions;
84418445
Q_FOREACH ( QgsMapLayer * layer, mLayerTreeView->selectedLayers() )
84428446
{
8443-
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer->id() );
8447+
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer );
84448448
if ( !tasks.isEmpty() )
84458449
{
84468450
Q_FOREACH ( QgsTask* task, tasks )
@@ -9645,7 +9649,7 @@ bool QgisApp::checkTasksDependOnProject()
96459649

96469650
for ( ; layerIt != layers.constEnd(); ++layerIt )
96479651
{
9648-
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.key() );
9652+
QList< QgsTask* > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.value() );
96499653
if ( !tasks.isEmpty() )
96509654
{
96519655
Q_FOREACH ( QgsTask* task, tasks )

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ SET(QGIS_CORE_SRCS
237237
qgsunittypes.cpp
238238
qgsvectordataprovider.cpp
239239
qgsvectorfilewriter.cpp
240+
qgsvectorfilewritertask.cpp
240241
qgsvectorlayer.cpp
241242
qgsvectorlayercache.cpp
242243
qgsvectorlayerdiagramprovider.cpp
@@ -528,6 +529,7 @@ SET(QGIS_CORE_MOC_HDRS
528529
qgstransactiongroup.h
529530
qgsunittypes.h
530531
qgsvectordataprovider.h
532+
qgsvectorfilewritertask.h
531533
qgsvectorlayercache.h
532534
qgsvectorlayereditbuffer.h
533535
qgsvectorlayereditpassthrough.h

‎src/core/qgsfeedback.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,44 @@ class CORE_EXPORT QgsFeedback : public QObject
6161
//! Tells whether the operation has been canceled already
6262
bool isCanceled() const { return mCanceled; }
6363

64+
/**
65+
* Sets the current progress for the feedback object. The \a progress
66+
* argument is in percentage and valid values range from 0-100.
67+
* @see progress()
68+
* @see progressChanged()
69+
* @note added in QGIS 3.0
70+
*/
71+
void setProgress( double progress ) { mProgress = progress; emit progressChanged( mProgress ); }
72+
73+
/**
74+
* Returns the current progress reported by the feedback object. Depending on how the
75+
* feedback object is used progress reporting may not be supported. The returned value
76+
* is in percentage and ranges from 0-100.
77+
* @see setProgress()
78+
* @see progressChanged()
79+
* @note added in QGIS 3.0
80+
*/
81+
double progress() const { return mProgress; }
82+
6483
signals:
6584
//! Internal routines can connect to this signal if they use event loop
6685
void canceled();
6786

87+
/**
88+
* Emitted when the feedback object reports a progress change. Depending on how the
89+
* feedback object is used progress reporting may not be supported. The \a progress
90+
* argument is in percentage and ranges from 0-100.
91+
* @note added in QGIS 3.0
92+
* @see setProgress()
93+
* @see progress()
94+
*/
95+
void progressChanged( double progress );
96+
6897
private:
6998
//! Whether the operation has been canceled already. False by default.
7099
bool mCanceled;
100+
101+
double mProgress = 0.0;
71102
};
72103

73104
#endif // QGSFEEDBACK_H

‎src/core/qgstaskmanager.cpp

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "qgstaskmanager.h"
1919
#include "qgsproject.h"
20+
#include "qgsmaplayerlistutils.h"
2021
#include <QtConcurrentRun>
2122

2223

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

139-
void QgsTask::setDependentLayers( const QStringList& dependentLayerIds )
140+
QList<QgsMapLayer*> QgsTask::dependentLayers() const
140141
{
141-
mDependentLayerIds = dependentLayerIds;
142+
return _qgis_listQPointerToRaw( mDependentLayers );
143+
}
144+
145+
void QgsTask::setDependentLayers( const QList< QgsMapLayer* >& dependentLayers )
146+
{
147+
mDependentLayers = _qgis_listRawToQPointer( dependentLayers );
142148
}
143149

144150
void QgsTask::subTaskStatusChanged( int status )
@@ -325,8 +331,8 @@ QgsTaskManager::QgsTaskManager( QObject* parent )
325331
, mTaskMutex( new QMutex( QMutex::Recursive ) )
326332
, mNextTaskId( 0 )
327333
{
328-
connect( QgsProject::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ),
329-
this, SLOT( layersWillBeRemoved( QStringList ) ) );
334+
connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer* >& ) > ( &QgsProject::layersWillBeRemoved ),
335+
this, &QgsTaskManager::layersWillBeRemoved );
330336
}
331337

332338
QgsTaskManager::~QgsTaskManager()
@@ -376,8 +382,8 @@ long QgsTaskManager::addTaskPrivate( QgsTask* task, QgsTaskList dependencies, bo
376382
{
377383
mParentTasks << task;
378384
}
379-
if ( !task->dependentLayerIds().isEmpty() )
380-
mLayerDependencies.insert( taskId, task->dependentLayerIds() );
385+
if ( !task->dependentLayers().isEmpty() )
386+
mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
381387
mTaskMutex->unlock();
382388

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

549-
QStringList QgsTaskManager::dependentLayers( long taskId ) const
555+
QList<QgsMapLayer*> QgsTaskManager::dependentLayers( long taskId ) const
550556
{
551557
QMutexLocker ml( mTaskMutex );
552-
return mLayerDependencies.value( taskId, QStringList() );
558+
return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
553559
}
554560

555-
QList<QgsTask*> QgsTaskManager::tasksDependentOnLayer( const QString& layerId ) const
561+
QList<QgsTask*> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer* layer ) const
556562
{
557563
QMutexLocker ml( mTaskMutex );
558564
QList< QgsTask* > tasks;
559-
QMap< long, QStringList >::const_iterator layerIt = mLayerDependencies.constBegin();
565+
QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
560566
for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
561567
{
562-
if ( layerIt.value().contains( layerId ) )
568+
if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
563569
{
564570
QgsTask* layerTask = task( layerIt.key() );
565571
if ( layerTask )
@@ -648,21 +654,21 @@ void QgsTaskManager::taskStatusChanged( int status )
648654

649655
}
650656

651-
void QgsTaskManager::layersWillBeRemoved( const QStringList& layerIds )
657+
void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer* >& layers )
652658
{
653659
mTaskMutex->lock();
654660
// scan through layers to be removed
655-
QMap< long, QStringList > layerDependencies = mLayerDependencies;
661+
QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
656662
layerDependencies.detach();
657663
mTaskMutex->unlock();
658664

659-
Q_FOREACH ( const QString& layerId, layerIds )
665+
Q_FOREACH ( QgsMapLayer* layer, layers )
660666
{
661667
// scan through tasks with layer dependencies
662-
for ( QMap< long, QStringList >::const_iterator it = layerDependencies.constBegin();
668+
for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
663669
it != layerDependencies.constEnd(); ++it )
664670
{
665-
if ( !it.value().contains( layerId ) )
671+
if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
666672
{
667673
//task not dependent on this layer
668674
continue;

0 commit comments

Comments
 (0)
Please sign in to comment.