Skip to content

Commit

Permalink
Fix layer paths resolving to matching CWD names
Browse files Browse the repository at this point in the history
- Remove redundant second call to absoluteToRelativeUri in QgsMapLayer::writeLayerXml
- Add a guard in QgsPathResolver::writePath to prevent this
  happening again when absoluteToRelativeUri is called two times.
- Add a regression test
  • Loading branch information
vsydorov committed Jan 10, 2025
1 parent a2c3471 commit f3149d9
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 2 deletions.
3 changes: 1 addition & 2 deletions src/core/qgsmaplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,8 +779,7 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) );
const QgsDataProvider *provider = dataProvider();
const QString providerKey = provider ? provider->name() : QString();
const QString srcRaw = encodedSource( source(), context );
const QString src = providerKey.isEmpty() ? srcRaw : QgsProviderRegistry::instance()->absoluteToRelativeUri( providerKey, srcRaw, context );
const QString src = encodedSource( source(), context );
const QDomText dataSourceText = document.createTextNode( src );
dataSource.appendChild( dataSourceText );
layerElement.appendChild( dataSource );
Expand Down
6 changes: 6 additions & 0 deletions src/core/qgspathresolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ QString QgsPathResolver::writePath( const QString &s ) const
}

const QFileInfo srcFileInfo( srcPath );
// Guard against relative paths: If srcPath is already relative, QFileInfo will match
// files in the working directory, instead of project directory. Avoid by returning early.
if ( !srcFileInfo.isAbsolute() )
{
return srcPath;
}
if ( srcFileInfo.exists() )
srcPath = srcFileInfo.canonicalFilePath();

Expand Down
80 changes: 80 additions & 0 deletions tests/src/core/testqgsproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class TestQgsProject : public QObject
void testAttachmentIdentifier();
void testEmbeddedGroupWithJoins();
void testAsynchronousLayerLoading();
void regression60100();
};

void TestQgsProject::init()
Expand Down Expand Up @@ -1103,5 +1104,84 @@ void TestQgsProject::testAsynchronousLayerLoading()
}


void TestQgsProject::regression60100()
{
/*
* Regression test for QGIS issue #60100 (https://github.com/qgis/QGIS/issues/60100)
* This test ensures that when saving a QGIS project with relative paths,
* the correct layer datasource is preserved, even when the current working
* directory (CWD) contains a file with the same name as the layer datasource.
*
* Previous behavior:
* - If a file with the same name as a layer datasource existed in the CWD,
* the layer path in the saved project would point to the file in the CWD,
* rather than the intended file in the project directory (PROJDIR).
*
* Test steps:
* 1. Create a temporary directory structure with two subfolders: WORKDIR and PROJDIR.
* 2. Copy a `points.geojson` file to both WORKDIR and PROJDIR.
* 3. Create a new QGIS project in PROJDIR and add the `points.geojson` file from PROJDIR as a layer.
* 4. Change the working directory to WORKDIR and save the project.
* 5. Verify that the saved project references the correct datasource (`./points.geojson` in PROJDIR)
* and does not erroneously reference the file in WORKDIR.
*/

// Create directory structure with 2 subfolders
const QTemporaryDir baseDir;
const QDir base( baseDir.path() );
base.mkdir( QStringLiteral( "WORKDIR" ) );
base.mkdir( QStringLiteral( "PROJDIR" ) );
const QString workDirPath = baseDir.path() + QStringLiteral( "/WORKDIR" );
const QString projDirPath = baseDir.path() + QStringLiteral( "/PROJDIR" );

// Save our old CWD and switch to the new WORKDIR
const QString oldCWD = QDir::currentPath();
QVERIFY( QDir::setCurrent( workDirPath ) );

// Copy points.geojson to both subfolders
const QString testDataDir( TEST_DATA_DIR );
const QString pointsPath = testDataDir + QStringLiteral( "/points.geojson" );
QFile::copy( pointsPath, workDirPath + QStringLiteral( "/points.geojson" ) );
QFile::copy( pointsPath, projDirPath + QStringLiteral( "/points.geojson" ) );

// Create a new/empty project in PROJDIR
const QString projectPath = projDirPath + QStringLiteral( "/project.qgs" );
std::unique_ptr<QgsProject> project = std::make_unique<QgsProject>();

// Add the local points.geojson (in PROJDIR) as a layer
std::unique_ptr<QgsVectorLayer> layer = std::make_unique<QgsVectorLayer>(
projDirPath + QStringLiteral( "/points.geojson" ),
QStringLiteral( "Test Points" ),
QStringLiteral( "ogr" )
);
project->addMapLayer( layer.release() );

// Write (save) the project to disk. This used to pick up the WRONG file and save it to the proj.
project->write( projectPath );

// Restore old working directory
QVERIFY( QDir::setCurrent( oldCWD ) );

// Verify the layer path in the project file
QDomDocument doc;
QFile projectFile( projectPath );
bool res = projectFile.open( QIODevice::ReadOnly );
Q_ASSERT( res );
res = static_cast<bool>( doc.setContent( &projectFile ) );
Q_ASSERT( res );
projectFile.close();

const QDomElement docElem = doc.documentElement();
const QDomElement layersElem = docElem.firstChildElement( QStringLiteral( "projectlayers" ) );
QDomElement layerElem = layersElem.firstChildElement();
while ( !layerElem.isNull() )
{
const QString layerSource = layerElem.firstChildElement( QStringLiteral( "datasource" ) ).text();
// Should NOT be "../WORKDIR/points.geojson"
QCOMPARE( layerSource, QStringLiteral( "./points.geojson" ) );
layerElem = layerElem.nextSiblingElement();
}
}

QGSTEST_MAIN( TestQgsProject )
#include "testqgsproject.moc"

0 comments on commit f3149d9

Please sign in to comment.