Skip to content

Commit 5b5cc50

Browse files
authored
Merge pull request #9620 from elpaso/bugfix-badlayers-storage
Bugfix badlayers storage
2 parents 20ec29a + bd9b373 commit 5b5cc50

File tree

6 files changed

+89
-15
lines changed

6 files changed

+89
-15
lines changed

python/core/auto_generated/qgsmaplayerstore.sip.in

+7
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ The layersAdded() and layerWasAdded() signals will always be emitted.
120120

121121
:param layers: A list of layer which should be added to the store.
122122

123+
.. note::
124+
125+
If a layer with the same id is already in the store it is not added again,
126+
but if the validity of the layer has changed from ``False`` to ``True``, the
127+
layer data source is updated to the new one.
128+
129+
123130
:return: a list of the map layers that were added
124131
successfully. If a layer already exists in the store,
125132
it will not be part of the returned list.

src/app/qgshandlebadlayers.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,6 @@ void QgsHandleBadLayers::apply()
366366
QHash<QString, QString> baseChange;
367367

368368

369-
370369
for ( int i = 0; i < mLayerList->rowCount(); i++ )
371370
{
372371
int idx = mLayerList->item( i, 0 )->data( Qt::UserRole ).toInt();
@@ -405,27 +404,27 @@ void QgsHandleBadLayers::apply()
405404
datasource = datasource.replace( basepath, baseChange[basepath] );
406405

407406

408-
bool dataSourceChanged { false };
407+
bool dataSourceFixed { false };
409408
const QString layerId { node.namedItem( QStringLiteral( "id" ) ).toElement().text() };
410409
const QString provider { node.namedItem( QStringLiteral( "provider" ) ).toElement().text() };
411410

412411
// Try first to change the datasource of the existing layers, this will
413412
// maintain the current status (checked/unchecked) and group
414413
if ( QgsProject::instance()->mapLayer( layerId ) )
415414
{
416-
QgsDataProvider::ProviderOptions options;
417415
QgsMapLayer *mapLayer = QgsProject::instance()->mapLayer( layerId );
418416
if ( mapLayer )
419417
{
418+
QgsDataProvider::ProviderOptions options;
420419
mapLayer->setDataSource( datasource, name, provider, options );
421-
dataSourceChanged = mapLayer->isValid();
420+
dataSourceFixed = mapLayer->isValid();
422421
}
423422
}
424423

425424
// If the data source was changed successfully, remove the bad layer from the dialog
426425
// otherwise, try to set the new datasource in the XML node and reload the layer,
427426
// finally marks with red all remaining bad layers.
428-
if ( dataSourceChanged )
427+
if ( dataSourceFixed )
429428
{
430429
mLayerList->removeRow( i-- );
431430
}
@@ -447,7 +446,8 @@ void QgsHandleBadLayers::apply()
447446
}
448447
}
449448

450-
// Final cleanup: remove any bad layer (it should not be any btw)
449+
// Final cleanup: remove any remaining bad layer
450+
// (there should not be any at this point)
451451
if ( mLayerList->rowCount() == 0 )
452452
{
453453
QList<QgsMapLayer *> toRemove;

src/core/qgsmaplayerstore.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ QList<QgsMapLayer *> QgsMapLayerStore::addMapLayers( const QList<QgsMapLayer *>
7474
QgsDebugMsg( QStringLiteral( "Cannot add null layers" ) );
7575
continue;
7676
}
77+
// If the layer is already in the store but its validity has flipped to TRUE reset data source
78+
if ( mMapLayers.contains( myLayer->id() ) && ! mMapLayers[myLayer->id()]->isValid() && myLayer->isValid() && myLayer->dataProvider() )
79+
{
80+
mMapLayers[myLayer->id()]->setDataSource( myLayer->dataProvider()->dataSourceUri(), myLayer->name(), myLayer->providerType(), QgsDataProvider::ProviderOptions() );
81+
}
7782
//check the layer is not already registered!
7883
if ( !mMapLayers.contains( myLayer->id() ) )
7984
{

src/core/qgsmaplayerstore.h

+5
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ class CORE_EXPORT QgsMapLayerStore : public QObject
149149
* If you specify FALSE here you have take care of deleting
150150
* the layers yourself. Not available in Python.
151151
*
152+
*
153+
* \note If a layer with the same id is already in the store it is not added again,
154+
* but if the validity of the layer has changed from FALSE to TRUE, the
155+
* layer data source is updated to the new one.
156+
*
152157
* \returns a list of the map layers that were added
153158
* successfully. If a layer already exists in the store,
154159
* it will not be part of the returned list.

src/core/qgsproject.cpp

+22-8
Original file line numberDiff line numberDiff line change
@@ -944,30 +944,30 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
944944
{
945945
QString type = layerElem.attribute( QStringLiteral( "type" ) );
946946
QgsDebugMsgLevel( "Layer type is " + type, 4 );
947-
QgsMapLayer *mapLayer = nullptr;
947+
std::unique_ptr<QgsMapLayer> mapLayer;
948948

949949
if ( type == QLatin1String( "vector" ) )
950950
{
951-
mapLayer = new QgsVectorLayer;
951+
mapLayer = qgis::make_unique<QgsVectorLayer>();
952952

953953
// apply specific settings to vector layer
954-
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer ) )
954+
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
955955
{
956956
vl->setReadExtentFromXml( mTrustLayerMetadata );
957957
}
958958
}
959959
else if ( type == QLatin1String( "raster" ) )
960960
{
961-
mapLayer = new QgsRasterLayer;
961+
mapLayer = qgis::make_unique<QgsRasterLayer>();
962962
}
963963
else if ( type == QLatin1String( "mesh" ) )
964964
{
965-
mapLayer = new QgsMeshLayer;
965+
mapLayer = qgis::make_unique<QgsMeshLayer>();
966966
}
967967
else if ( type == QLatin1String( "plugin" ) )
968968
{
969969
QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
970-
mapLayer = QgsApplication::pluginLayerRegistry()->createLayer( typeName );
970+
mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
971971
}
972972

973973
if ( !mapLayer )
@@ -978,13 +978,19 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
978978

979979
Q_CHECK_PTR( mapLayer ); // NOLINT
980980

981+
// This is tricky: to avoid a leak we need to check if the layer was already in the store
982+
// because if it was, the newly created layer will not be added to the store and it would leak.
983+
const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
984+
Q_ASSERT( ! layerId.isEmpty() );
985+
const bool layerWasStored { layerStore()->mapLayer( layerId ) };
986+
981987
// have the layer restore state that is stored in Dom node
982988
bool layerIsValid = mapLayer->readLayerXml( layerElem, context ) && mapLayer->isValid();
983989
QList<QgsMapLayer *> newLayers;
984-
newLayers << mapLayer;
990+
newLayers << mapLayer.get();
985991
if ( layerIsValid )
986992
{
987-
emit readMapLayer( mapLayer, layerElem );
993+
emit readMapLayer( mapLayer.get(), layerElem );
988994
addMapLayers( newLayers );
989995
}
990996
else
@@ -995,6 +1001,14 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
9951001
QgsDebugMsg( "Unable to load " + type + " layer" );
9961002
brokenNodes.push_back( layerElem );
9971003
}
1004+
1005+
// It should be safe to delete the layer now if layer was stored, because all the store
1006+
// had to to was to reset the data source in case the validity changed.
1007+
if ( ! layerWasStored )
1008+
{
1009+
mapLayer.release();
1010+
}
1011+
9981012
return layerIsValid;
9991013
}
10001014

tests/src/python/test_qgsmaplayerstore.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@
1212
# This will get replaced with a git SHA1 when you do a git archive
1313
__revision__ = '$Format:%H$'
1414

15-
from qgis.core import QgsMapLayerStore, QgsVectorLayer, QgsMapLayer
15+
import os
16+
from qgis.core import (
17+
QgsMapLayerStore,
18+
QgsVectorLayer,
19+
QgsMapLayer,
20+
QgsDataProvider,
21+
QgsProject,
22+
QgsReadWriteContext,
23+
)
1624
from qgis.testing import start_app, unittest
1725
from qgis.PyQt.QtCore import QT_VERSION_STR
1826
from qgis.PyQt import sip
1927
from qgis.PyQt.QtTest import QSignalSpy
28+
from qgis.PyQt.QtXml import QDomDocument, QDomNode
2029
from time import sleep
30+
from utilities import unitTestDataPath
2131

2232
start_app()
33+
TEST_DATA_DIR = unitTestDataPath()
2334

2435

2536
def createLayer(name):
@@ -529,6 +540,38 @@ def testTransferLayers(self):
529540
self.assertEqual(len(store1.mapLayers()), 3)
530541
self.assertEqual(store1.mapLayers(), {l1.id(): l1, l2.id(): l2, l3.id(): l3})
531542

543+
def testLayerDataSourceReset(self):
544+
"""When adding a layer with the same id to the store make sure
545+
the data source is also updated in case the layer validity has
546+
changed from False to True"""
547+
548+
p = QgsProject()
549+
store = p.layerStore()
550+
vl1 = createLayer('valid')
551+
vl2 = QgsVectorLayer('/not_a_valid_path.shp', 'invalid', 'ogr')
552+
self.assertTrue(vl1.isValid())
553+
self.assertFalse(vl2.isValid())
554+
store.addMapLayers([vl1, vl2])
555+
self.assertEqual(store.validCount(), 1)
556+
self.assertEqual(len(store.mapLayers()), 2)
557+
558+
# Re-add the bad layer
559+
store.addMapLayers([vl2])
560+
self.assertEqual(store.validCount(), 1)
561+
self.assertEqual(len(store.mapLayers()), 2)
562+
563+
doc = QDomDocument()
564+
doc.setContent('<maplayer><provider encoding="UTF-8">ogr</provider><layername>fixed</layername><id>%s</id></maplayer>' % vl2.id())
565+
layer_node = QDomNode(doc.firstChild())
566+
self.assertTrue(vl2.writeXml(layer_node, doc, QgsReadWriteContext()))
567+
datasource_node = doc.createElement("datasource")
568+
datasource_node.appendChild(doc.createTextNode(os.path.join(TEST_DATA_DIR, 'points.shp')))
569+
layer_node.appendChild(datasource_node)
570+
p.readLayer(layer_node)
571+
self.assertEqual(store.validCount(), 2)
572+
self.assertEqual(len(store.mapLayers()), 2)
573+
self.assertEqual(store.mapLayers()[vl2.id()].name(), 'fixed')
574+
532575

533576
if __name__ == '__main__':
534577
unittest.main()

0 commit comments

Comments
 (0)