From ddb78041b9bdafd5dfe61d078383f296ce1763c9 Mon Sep 17 00:00:00 2001 From: Richard Kello Date: Thu, 24 Sep 2026 16:16:34 +0200 Subject: [PATCH] Fix incorrect sequence number placement --- app/attributes/attributecontroller.cpp | 2 +- app/test/testattributecontroller.cpp | 44 ++++++++++++++++++++++++++ app/test/testattributecontroller.h | 3 ++ app/test/testcoreutils.cpp | 2 +- core/coreutils.cpp | 3 +- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/app/attributes/attributecontroller.cpp b/app/attributes/attributecontroller.cpp index 6c071485a..acfe0ab6f 100644 --- a/app/attributes/attributecontroller.cpp +++ b/app/attributes/attributecontroller.cpp @@ -1649,7 +1649,7 @@ void AttributeController::renamePhotos() InputUtils::sanitizePath( newName ); const QFileInfo fi( src ); - newName = QStringLiteral( "%1.%2" ).arg( newName, fi.completeSuffix() ); + newName = QStringLiteral( "%1.%2" ).arg( newName, fi.suffix() ); const QString dst = CoreUtils::findUniquePath( InputUtils::getAbsolutePath( newName, targetDir ) ); if ( InputUtils::renameFile( src, dst ) ) diff --git a/app/test/testattributecontroller.cpp b/app/test/testattributecontroller.cpp index 2145bb058..2c807a4fb 100644 --- a/app/test/testattributecontroller.cpp +++ b/app/test/testattributecontroller.cpp @@ -894,6 +894,50 @@ void TestAttributeController::testPhotoRenaming() } } +void TestAttributeController::testPhotoRenamingCollisionWithDotInName() +{ + QString projectName = QStringLiteral( "testPhotoRenamingCollisionWithDotInName" ); + QString projectDir = QDir::tempPath() + "/MM_test_projects/" + projectName; + + QDir tempDir( projectDir ); + QVERIFY( tempDir.removeRecursively() ); + + QVERIFY( InputUtils::cpDir( TestUtils::testDataDir() + "/test_photo_rename", projectDir ) ); + QVERIFY( QFile::exists( projectDir + QStringLiteral( "/image1.jpg" ) ) ); + + // "photo" naming expression is 'image_' + "notes", so a dot in notes lands in the name too + const QString collidingPath = projectDir + QStringLiteral( "/image_my.notes.jpg" ); + QVERIFY( QFile::copy( projectDir + QStringLiteral( "/image1.jpg" ), collidingPath ) ); + QVERIFY( QFile::exists( collidingPath ) ); + + QVERIFY( QgsProject::instance()->read( projectDir + QStringLiteral( "/test_photo_rename.qgz" ) ) ); + + QgsMapLayer *layer = QgsProject::instance()->mapLayersByName( QStringLiteral( "Survey" ) ).at( 0 ); + QgsVectorLayer *surveyLayer = static_cast( layer ); + QVERIFY( surveyLayer && surveyLayer->isValid() ); + + QgsFeature feat( surveyLayer->fields() ); + FeatureLayerPair pair( feat, surveyLayer ); + + AttributeController controller; + controller.setFeatureLayerPair( pair ); + + const TabItem *tab = controller.tabItem( 0 ); + const QVector items = tab->formItems(); + + controller.setFormValue( items.at( 2 ), QStringLiteral( "my.notes" ) ); + controller.setFormValue( items.at( 3 ), QStringLiteral( "image1.jpg" ) ); + + controller.save(); + + const QgsFeature f = controller.featureLayerPair().feature(); + + QVERIFY( QFile::exists( collidingPath ) ); // untouched + QVERIFY( !QFile::exists( projectDir + QStringLiteral( "/image_my (1).notes.jpg" ) ) ); + QVERIFY( QFile::exists( projectDir + QStringLiteral( "/image_my.notes (1).jpg" ) ) ); + QCOMPARE( f.attribute( 3 ), QStringLiteral( "image_my.notes (1).jpg" ) ); +} + void TestAttributeController::testHtmlAndTextWidgets() { QString projectDir = TestUtils::testDataDir() + "/expressions"; diff --git a/app/test/testattributecontroller.h b/app/test/testattributecontroller.h index 5fa25b9dc..a7b0bdd1d 100644 --- a/app/test/testattributecontroller.h +++ b/app/test/testattributecontroller.h @@ -28,6 +28,9 @@ class TestAttributeController: public QObject void testRawValue(); void testFieldsOutsideForm(); void testPhotoRenaming(); + + //! A collision suffix must be inserted before the real extension, even if the name has a dot + void testPhotoRenamingCollisionWithDotInName(); void testHtmlAndTextWidgets(); void testVirtualFields(); diff --git a/app/test/testcoreutils.cpp b/app/test/testcoreutils.cpp index b4d8117b4..ecf23d567 100644 --- a/app/test/testcoreutils.cpp +++ b/app/test/testcoreutils.cpp @@ -139,7 +139,7 @@ void TestCoreUtils::testFindUniquePath() { "folderA", "folderA (1)" }, { "non.txt", "non.txt" }, { "data.gpkg", "data.gpkg" }, - { "arch.tar.gz", "arch (1).tar.gz" }, + { "arch.tar.gz", "arch.tar (1).gz" }, { "folderA/folder", "folderA/folder" }, { "folderA/fileA.txt", "folderA/fileA (2).txt" }, { "folderA/fileB.txt", "folderA/fileB (1).txt" }, diff --git a/core/coreutils.cpp b/core/coreutils.cpp index 143e1f81b..22eef3f86 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -186,7 +186,8 @@ QString CoreUtils::findUniquePath( const QString &path ) } else // file { - uniquePath = originalPath.path() + '/' + originalPath.baseName() + " (" + QString::number( i ) + ")." + originalPath.completeSuffix(); + // split on the last dot, so a dot in the base name isn't mistaken for the extension + uniquePath = originalPath.path() + '/' + originalPath.completeBaseName() + " (" + QString::number( i ) + ")." + originalPath.suffix(); } f.setFile( uniquePath ); }