Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Also support project attachments for QGS format
  • Loading branch information
manisandro committed Jun 22, 2021
1 parent 001d363 commit 6f0bac4
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 26 deletions.
8 changes: 0 additions & 8 deletions python/core/auto_generated/project/qgsproject.sip.in
Expand Up @@ -1254,21 +1254,13 @@ Attaches a file to the project

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

.. note::

Attached files are only supported by QGZ file based projects

.. versionadded:: 3.22
%End

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

.. note::

Attached files are only supported by QGZ file based projects

.. seealso:: :py:func:`createAttachedFile`

.. versionadded:: 3.22
Expand Down
52 changes: 39 additions & 13 deletions src/core/project/qgsproject.cpp
Expand Up @@ -375,7 +375,7 @@ QgsProject::QgsProject( QObject *parent )
, mDisplaySettings( new QgsProjectDisplaySettings( this ) )
, mRootGroup( new QgsLayerTree )
, mLabelingEngineSettings( new QgsLabelingEngineSettings )
, mArchive( new QgsProjectArchive() )
, mArchive( new QgsArchive() )
, mAuxiliaryStorage( new QgsAuxiliaryStorage() )
{
mProperties.setName( QStringLiteral( "properties" ) );
Expand Down Expand Up @@ -829,7 +829,7 @@ void QgsProject::clear()
mLabelingEngineSettings->clear();

mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
mArchive.reset( new QgsProjectArchive() );
mArchive.reset( new QgsArchive() );

emit labelingEngineSettingsChanged();

Expand Down Expand Up @@ -1294,6 +1294,16 @@ bool QgsProject::read( QgsProject::ReadFlags flags )
else
{
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
QFileInfo finfo( mFile.fileName() );
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
if ( QFile( attachmentsZip ).exists() )
{
std::unique_ptr<QgsArchive> archive( new QgsArchive() );
if ( archive->unzip( attachmentsZip ) )
{
mArchive = std::move( archive );
}
}
returnValue = readProjectFile( mFile.fileName(), flags );
}

Expand Down Expand Up @@ -1391,7 +1401,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
profile.switchTask( tr( "Creating auxiliary storage" ) );
QString fileName = mFile.fileName();
std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
std::unique_ptr<QgsProjectArchive> archive = std::move( mArchive );
std::unique_ptr<QgsArchive> archive = std::move( mArchive );
clear();
mAuxiliaryStorage = std::move( aStorage );
mArchive = std::move( archive );
Expand Down Expand Up @@ -2162,15 +2172,31 @@ bool QgsProject::write()
// saved
const bool asOk = saveAuxiliaryStorage();
const bool writeOk = writeProjectFile( mFile.fileName() );
bool attachmentsOk = true;
if ( !mArchive->files().isEmpty() )
{
QFileInfo finfo( mFile.fileName() );
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
attachmentsOk = mArchive->zip( attachmentsZip );
}

// errors raised during writing project file are more important
if ( !asOk && writeOk )
if ( ( !asOk || !attachmentsOk ) && writeOk )
{
const QString err = mAuxiliaryStorage->errorString();
setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
QStringList errorMessage;
if ( !asOk )
{
const QString err = mAuxiliaryStorage->errorString();
errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
}
if ( !attachmentsOk )
{
errorMessage.append( tr( "Unable to save attachments archive" ) );
}
setError( errorMessage.join( "\n" ) );
}

return asOk && writeOk;
return asOk && writeOk && attachmentsOk;
}
}

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

// load auxiliary storage
if ( !mArchive->auxiliaryStorageFile().isEmpty() )
if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
{
// database file is already a copy as it's been unzipped. So we don't open
// auxiliary storage in copy mode in this case
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( mArchive->auxiliaryStorageFile(), false ) );
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
}
else
{
mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
}

// read the project file
if ( ! readProjectFile( mArchive->projectFile(), flags ) )
if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
{
setError( tr( "Cannot read unzipped qgs project file" ) );
return false;
}

// Remove the temporary .qgs file
mArchive->clearProjectFile();
static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();

return true;
}
Expand Down Expand Up @@ -3360,9 +3386,9 @@ bool QgsProject::zip( const QString &filename )
{
mArchive.reset( new QgsProjectArchive() );
mArchive->unzip( mFile.fileName() );
mArchive->clearProjectFile();
static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();

const QString auxiliaryStorageFile = mArchive->auxiliaryStorageFile();
const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
if ( ! auxiliaryStorageFile.isEmpty() )
{
archive->addFile( auxiliaryStorageFile );
Expand Down
4 changes: 1 addition & 3 deletions src/core/project/qgsproject.h
Expand Up @@ -1323,15 +1323,13 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
* Attaches a file to the project
* \param nameTemplate Any filename template, used as a basename for attachment file, i.e. "myfile.ext"
* \return The path to the file where the contents can be written to.
* \note Attached files are only supported by QGZ file based projects
* \since QGIS 3.22
*/
QString createAttachedFile( const QString &nameTemplate );

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

QVariantMap mCustomVariables;

std::unique_ptr<QgsProjectArchive> mArchive;
std::unique_ptr<QgsArchive> mArchive;

std::unique_ptr<QgsAuxiliaryStorage> mAuxiliaryStorage;

Expand Down
83 changes: 81 additions & 2 deletions tests/src/core/testqgsproject.cpp
Expand Up @@ -55,7 +55,8 @@ class TestQgsProject : public QObject
void testCrsExpressions();
void testCrsValidAfterReadingProjectFile();
void testDefaultRelativePaths();
void testAttachments();
void testAttachmentsQgs();
void testAttachmentsQgz();
};

void TestQgsProject::init()
Expand Down Expand Up @@ -719,7 +720,85 @@ void TestQgsProject::testDefaultRelativePaths()
QCOMPARE( p1PathsAbsolute_2, true );
}

void TestQgsProject::testAttachments()
void TestQgsProject::testAttachmentsQgs()
{
// Test QgsProject::{createAttachedFile,attachedFiles,removeAttachedFile}
{
QgsProject p;

QString fileName = p.createAttachedFile( "myattachment" );
QVERIFY( QFile( fileName ).exists() );
QVERIFY( p.attachedFiles().contains( fileName ) );
QVERIFY( p.removeAttachedFile( fileName ) );
QVERIFY( !p.attachedFiles().contains( fileName ) );
QVERIFY( !p.removeAttachedFile( fileName ) );
}

// Verify that attachment is exists after re-reading project
{
QTemporaryFile projFile( QDir::temp().absoluteFilePath( "XXXXXX_test.qgs" ) );
projFile.open();

QgsProject p;
QFile file;
QString fileName = p.createAttachedFile( "myattachment" );

file.setFileName( fileName );
QVERIFY( file.open( QIODevice::WriteOnly ) );
file.write( "Attachment" );
file.close();

p.write( projFile.fileName() );

QFileInfo finfo( projFile.fileName() );
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
QVERIFY( QFile( attachmentsZip ).exists() );

QgsProject p2;
p2.read( projFile.fileName() );
QVERIFY( p2.attachedFiles().size() == 1 );

file.setFileName( p2.attachedFiles().at( 0 ) );
QVERIFY( file.open( QIODevice::ReadOnly ) );
QVERIFY( file.readAll() == QByteArray( "Attachment" ) );
}

// Verify that attachment paths can be used as layer filenames
{
QTemporaryFile projFile( QDir::temp().absoluteFilePath( "XXXXXX_test.qgs" ) );
projFile.open();

QgsProject p;
QString fileName = p.createAttachedFile( "testlayer.gpx" );
QFile file( fileName );
file.open( QIODevice::WriteOnly );
file.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
file.write( "<gpx version=\"1.0\">" );
file.write( "<name>Example gpx</name>" );
file.write( "<wpt lat=\"0.0\" lon=\"0.0\">" );
file.write( "<name>NULL Island</name>" );
file.write( "</wpt>" );
file.write( "</gpx>" );
file.close();

QgsVectorLayer *layer = new QgsVectorLayer( fileName, "gpx" );
p.addMapLayer( layer );
p.write( projFile.fileName() );

QFileInfo finfo( projFile.fileName() );
QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
QVERIFY( QFile( attachmentsZip ).exists() );

QgsProject p2;
p2.read( projFile.fileName() );
QVERIFY( p2.attachedFiles().size() == 1 );
QVERIFY( p2.mapLayers().size() == 1 );
QVERIFY( p2.mapLayer( p2.mapLayers().firstKey() )->source() == p2.attachedFiles().first() );
}

}

void TestQgsProject::testAttachmentsQgz()
{
// Test QgsProject::{createAttachedFile,attachedFiles,removeAttachedFile}
{
Expand Down

0 comments on commit 6f0bac4

Please sign in to comment.