Skip to content

Commit 6f0bac4

Browse files
committedJun 22, 2021
Also support project attachments for QGS format
1 parent 001d363 commit 6f0bac4

File tree

4 files changed

+121
-26
lines changed

4 files changed

+121
-26
lines changed
 

‎python/core/auto_generated/project/qgsproject.sip.in

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,21 +1254,13 @@ Attaches a file to the project
12541254

12551255
:return: The path to the file where the contents can be written to.
12561256

1257-
.. note::
1258-
1259-
Attached files are only supported by QGZ file based projects
1260-
12611257
.. versionadded:: 3.22
12621258
%End
12631259

12641260
QStringList attachedFiles() const;
12651261
%Docstring
12661262
Returns a map of all attached files with identifier and real paths.
12671263

1268-
.. note::
1269-
1270-
Attached files are only supported by QGZ file based projects
1271-
12721264
.. seealso:: :py:func:`createAttachedFile`
12731265

12741266
.. versionadded:: 3.22

‎src/core/project/qgsproject.cpp

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ QgsProject::QgsProject( QObject *parent )
375375
, mDisplaySettings( new QgsProjectDisplaySettings( this ) )
376376
, mRootGroup( new QgsLayerTree )
377377
, mLabelingEngineSettings( new QgsLabelingEngineSettings )
378-
, mArchive( new QgsProjectArchive() )
378+
, mArchive( new QgsArchive() )
379379
, mAuxiliaryStorage( new QgsAuxiliaryStorage() )
380380
{
381381
mProperties.setName( QStringLiteral( "properties" ) );
@@ -829,7 +829,7 @@ void QgsProject::clear()
829829
mLabelingEngineSettings->clear();
830830

831831
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
832-
mArchive.reset( new QgsProjectArchive() );
832+
mArchive.reset( new QgsArchive() );
833833

834834
emit labelingEngineSettingsChanged();
835835

@@ -1294,6 +1294,16 @@ bool QgsProject::read( QgsProject::ReadFlags flags )
12941294
else
12951295
{
12961296
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1297+
QFileInfo finfo( mFile.fileName() );
1298+
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
1299+
if ( QFile( attachmentsZip ).exists() )
1300+
{
1301+
std::unique_ptr<QgsArchive> archive( new QgsArchive() );
1302+
if ( archive->unzip( attachmentsZip ) )
1303+
{
1304+
mArchive = std::move( archive );
1305+
}
1306+
}
12971307
returnValue = readProjectFile( mFile.fileName(), flags );
12981308
}
12991309

@@ -1391,7 +1401,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
13911401
profile.switchTask( tr( "Creating auxiliary storage" ) );
13921402
QString fileName = mFile.fileName();
13931403
std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
1394-
std::unique_ptr<QgsProjectArchive> archive = std::move( mArchive );
1404+
std::unique_ptr<QgsArchive> archive = std::move( mArchive );
13951405
clear();
13961406
mAuxiliaryStorage = std::move( aStorage );
13971407
mArchive = std::move( archive );
@@ -2162,15 +2172,31 @@ bool QgsProject::write()
21622172
// saved
21632173
const bool asOk = saveAuxiliaryStorage();
21642174
const bool writeOk = writeProjectFile( mFile.fileName() );
2175+
bool attachmentsOk = true;
2176+
if ( !mArchive->files().isEmpty() )
2177+
{
2178+
QFileInfo finfo( mFile.fileName() );
2179+
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
2180+
attachmentsOk = mArchive->zip( attachmentsZip );
2181+
}
21652182

21662183
// errors raised during writing project file are more important
2167-
if ( !asOk && writeOk )
2184+
if ( ( !asOk || !attachmentsOk ) && writeOk )
21682185
{
2169-
const QString err = mAuxiliaryStorage->errorString();
2170-
setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2186+
QStringList errorMessage;
2187+
if ( !asOk )
2188+
{
2189+
const QString err = mAuxiliaryStorage->errorString();
2190+
errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2191+
}
2192+
if ( !attachmentsOk )
2193+
{
2194+
errorMessage.append( tr( "Unable to save attachments archive" ) );
2195+
}
2196+
setError( errorMessage.join( "\n" ) );
21712197
}
21722198

2173-
return asOk && writeOk;
2199+
return asOk && writeOk && attachmentsOk;
21742200
}
21752201
}
21762202

@@ -3295,26 +3321,26 @@ bool QgsProject::unzip( const QString &filename, QgsProject::ReadFlags flags )
32953321
mArchive = std::move( archive );
32963322

32973323
// load auxiliary storage
3298-
if ( !mArchive->auxiliaryStorageFile().isEmpty() )
3324+
if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
32993325
{
33003326
// database file is already a copy as it's been unzipped. So we don't open
33013327
// auxiliary storage in copy mode in this case
3302-
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( mArchive->auxiliaryStorageFile(), false ) );
3328+
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
33033329
}
33043330
else
33053331
{
33063332
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
33073333
}
33083334

33093335
// read the project file
3310-
if ( ! readProjectFile( mArchive->projectFile(), flags ) )
3336+
if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
33113337
{
33123338
setError( tr( "Cannot read unzipped qgs project file" ) );
33133339
return false;
33143340
}
33153341

33163342
// Remove the temporary .qgs file
3317-
mArchive->clearProjectFile();
3343+
static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
33183344

33193345
return true;
33203346
}
@@ -3360,9 +3386,9 @@ bool QgsProject::zip( const QString &filename )
33603386
{
33613387
mArchive.reset( new QgsProjectArchive() );
33623388
mArchive->unzip( mFile.fileName() );
3363-
mArchive->clearProjectFile();
3389+
static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
33643390

3365-
const QString auxiliaryStorageFile = mArchive->auxiliaryStorageFile();
3391+
const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
33663392
if ( ! auxiliaryStorageFile.isEmpty() )
33673393
{
33683394
archive->addFile( auxiliaryStorageFile );

‎src/core/project/qgsproject.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,15 +1323,13 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
13231323
* Attaches a file to the project
13241324
* \param nameTemplate Any filename template, used as a basename for attachment file, i.e. "myfile.ext"
13251325
* \return The path to the file where the contents can be written to.
1326-
* \note Attached files are only supported by QGZ file based projects
13271326
* \since QGIS 3.22
13281327
*/
13291328
QString createAttachedFile( const QString &nameTemplate );
13301329

13311330
/**
13321331
* Returns a map of all attached files with identifier and real paths.
13331332
*
1334-
* \note Attached files are only supported by QGZ file based projects
13351333
* \see createAttachedFile()
13361334
* \since QGIS 3.22
13371335
*/
@@ -2030,7 +2028,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
20302028

20312029
QVariantMap mCustomVariables;
20322030

2033-
std::unique_ptr<QgsProjectArchive> mArchive;
2031+
std::unique_ptr<QgsArchive> mArchive;
20342032

20352033
std::unique_ptr<QgsAuxiliaryStorage> mAuxiliaryStorage;
20362034

‎tests/src/core/testqgsproject.cpp

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class TestQgsProject : public QObject
5555
void testCrsExpressions();
5656
void testCrsValidAfterReadingProjectFile();
5757
void testDefaultRelativePaths();
58-
void testAttachments();
58+
void testAttachmentsQgs();
59+
void testAttachmentsQgz();
5960
};
6061

6162
void TestQgsProject::init()
@@ -719,7 +720,85 @@ void TestQgsProject::testDefaultRelativePaths()
719720
QCOMPARE( p1PathsAbsolute_2, true );
720721
}
721722

722-
void TestQgsProject::testAttachments()
723+
void TestQgsProject::testAttachmentsQgs()
724+
{
725+
// Test QgsProject::{createAttachedFile,attachedFiles,removeAttachedFile}
726+
{
727+
QgsProject p;
728+
729+
QString fileName = p.createAttachedFile( "myattachment" );
730+
QVERIFY( QFile( fileName ).exists() );
731+
QVERIFY( p.attachedFiles().contains( fileName ) );
732+
QVERIFY( p.removeAttachedFile( fileName ) );
733+
QVERIFY( !p.attachedFiles().contains( fileName ) );
734+
QVERIFY( !p.removeAttachedFile( fileName ) );
735+
}
736+
737+
// Verify that attachment is exists after re-reading project
738+
{
739+
QTemporaryFile projFile( QDir::temp().absoluteFilePath( "XXXXXX_test.qgs" ) );
740+
projFile.open();
741+
742+
QgsProject p;
743+
QFile file;
744+
QString fileName = p.createAttachedFile( "myattachment" );
745+
746+
file.setFileName( fileName );
747+
QVERIFY( file.open( QIODevice::WriteOnly ) );
748+
file.write( "Attachment" );
749+
file.close();
750+
751+
p.write( projFile.fileName() );
752+
753+
QFileInfo finfo( projFile.fileName() );
754+
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
755+
QVERIFY( QFile( attachmentsZip ).exists() );
756+
757+
QgsProject p2;
758+
p2.read( projFile.fileName() );
759+
QVERIFY( p2.attachedFiles().size() == 1 );
760+
761+
file.setFileName( p2.attachedFiles().at( 0 ) );
762+
QVERIFY( file.open( QIODevice::ReadOnly ) );
763+
QVERIFY( file.readAll() == QByteArray( "Attachment" ) );
764+
}
765+
766+
// Verify that attachment paths can be used as layer filenames
767+
{
768+
QTemporaryFile projFile( QDir::temp().absoluteFilePath( "XXXXXX_test.qgs" ) );
769+
projFile.open();
770+
771+
QgsProject p;
772+
QString fileName = p.createAttachedFile( "testlayer.gpx" );
773+
QFile file( fileName );
774+
file.open( QIODevice::WriteOnly );
775+
file.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
776+
file.write( "<gpx version=\"1.0\">" );
777+
file.write( "<name>Example gpx</name>" );
778+
file.write( "<wpt lat=\"0.0\" lon=\"0.0\">" );
779+
file.write( "<name>NULL Island</name>" );
780+
file.write( "</wpt>" );
781+
file.write( "</gpx>" );
782+
file.close();
783+
784+
QgsVectorLayer *layer = new QgsVectorLayer( fileName, "gpx" );
785+
p.addMapLayer( layer );
786+
p.write( projFile.fileName() );
787+
788+
QFileInfo finfo( projFile.fileName() );
789+
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
790+
QVERIFY( QFile( attachmentsZip ).exists() );
791+
792+
QgsProject p2;
793+
p2.read( projFile.fileName() );
794+
QVERIFY( p2.attachedFiles().size() == 1 );
795+
QVERIFY( p2.mapLayers().size() == 1 );
796+
QVERIFY( p2.mapLayer( p2.mapLayers().firstKey() )->source() == p2.attachedFiles().first() );
797+
}
798+
799+
}
800+
801+
void TestQgsProject::testAttachmentsQgz()
723802
{
724803
// Test QgsProject::{createAttachedFile,attachedFiles,removeAttachedFile}
725804
{

0 commit comments

Comments
 (0)
Please sign in to comment.