mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-01 00:46:20 -05:00
Merge pull request #10002 from m-kuhn/qgz-attachments
Allow adding attachments in qgz files
This commit is contained in:
commit
0804e342c8
@ -974,6 +974,34 @@ provider.
|
||||
Returns the current auxiliary storage.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
QString attachedFile( const QString &fileName ) const;
|
||||
%Docstring
|
||||
Returns the path to an attached file known by ``fileName``.
|
||||
|
||||
.. note::
|
||||
|
||||
Attached files are only supported by QGZ file based projects
|
||||
|
||||
.. seealso:: :py:func:`collectAttachedFiles`
|
||||
|
||||
.. versionadded:: 3.8
|
||||
%End
|
||||
|
||||
QgsStringMap attachedFiles() const;
|
||||
%Docstring
|
||||
Returns a map of all attached files with relative paths and real paths.
|
||||
|
||||
.. note::
|
||||
|
||||
Attached files are only supported by QGZ file based projects
|
||||
|
||||
.. seealso:: :py:func:`collectAttachedFiles`
|
||||
|
||||
.. seealso:: :py:func:`attachedFile`
|
||||
|
||||
.. versionadded:: 3.8
|
||||
%End
|
||||
|
||||
const QgsProjectMetadata &metadata() const;
|
||||
@ -1390,6 +1418,31 @@ Emitted when the project dirty status changes.
|
||||
:param dirty: ``True`` if the project is in a dirty state and has pending unsaved changes.
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
void collectAttachedFiles( QgsStringMap &files /In,Out/ );
|
||||
%Docstring
|
||||
Emitted whenever the project is saved to a qgz file.
|
||||
This can be used to package additional files into the qgz file by modifying the ``files`` map.
|
||||
|
||||
Map keys represent relative paths inside the qgz file, map values represent the path to
|
||||
the source file.
|
||||
|
||||
In python, append additional files to the map and return the modified map.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
QgsProject.instance().collectAttachedFiles.connect(lambda files: files + ['/absolute/path/to/my/attachment.txt'])
|
||||
|
||||
.. note::
|
||||
|
||||
Only will be emitted with QGZ project files
|
||||
|
||||
.. seealso:: :py:func:`attachedFiles`
|
||||
|
||||
.. seealso:: :py:func:`attachedFile`
|
||||
|
||||
.. versionadded:: 3.8
|
||||
%End
|
||||
|
||||
public slots:
|
||||
|
@ -39,7 +39,7 @@ Unzip a zip file in an output directory.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
bool zip( const QString &zip, const QStringList &files );
|
||||
bool zip( const QString &zip, const QStringList &files, const QString &root = QString() );
|
||||
%Docstring
|
||||
Zip the list of files in the zip file. If the zip file already exists or is
|
||||
empty, an error is returned. If an input file does not exist, an error is
|
||||
@ -47,6 +47,7 @@ also returned.
|
||||
|
||||
:param zip: The zip filename
|
||||
:param files: The absolute path to files to embed within the zip
|
||||
:param root: The root path in which the files are located. This is used to determine the relative path inside the zip file.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
@ -61,7 +61,7 @@ bool QgsArchive::zip( const QString &filename )
|
||||
QFile tmpFile( tempPath + QDir::separator() + uuid );
|
||||
|
||||
// zip content
|
||||
if ( ! QgsZipUtils::zip( tmpFile.fileName(), mFiles ) )
|
||||
if ( ! QgsZipUtils::zip( tmpFile.fileName(), mFiles, dir() ) )
|
||||
{
|
||||
QString err = QObject::tr( "Unable to zip content" );
|
||||
QgsMessageLog::logMessage( err, QStringLiteral( "QgsArchive" ) );
|
||||
|
@ -2763,10 +2763,26 @@ bool QgsProject::zip( const QString &filename )
|
||||
return false;
|
||||
}
|
||||
|
||||
QgsStringMap attachedFiles;
|
||||
emit collectAttachedFiles( attachedFiles );
|
||||
|
||||
// create the archive
|
||||
archive->addFile( qgsFile.fileName() );
|
||||
archive->addFile( asFileName );
|
||||
|
||||
// add additional collected attachment files
|
||||
auto attachedFilesIterator = attachedFiles.constBegin();
|
||||
while ( attachedFilesIterator != attachedFiles.constEnd() )
|
||||
{
|
||||
QString filepath = info.path() + QDir::separator() + attachedFilesIterator.key();
|
||||
QDir().mkpath( QFileInfo( filepath ).dir().path() );
|
||||
if ( QFile::copy( attachedFilesIterator.value(), filepath ) )
|
||||
archive->addFile( filepath );
|
||||
else
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Could not copy file '%1' to '%2'" ).arg( attachedFilesIterator.value(), filepath ) );
|
||||
++attachedFilesIterator;
|
||||
}
|
||||
|
||||
// zip
|
||||
if ( !archive->zip( filename ) )
|
||||
{
|
||||
@ -2956,6 +2972,21 @@ QgsAuxiliaryStorage *QgsProject::auxiliaryStorage()
|
||||
return mAuxiliaryStorage.get();
|
||||
}
|
||||
|
||||
QString QgsProject::attachedFile( const QString &fileName ) const
|
||||
{
|
||||
return mArchive->dir() + QDir::separator() + fileName;
|
||||
}
|
||||
|
||||
QgsStringMap QgsProject::attachedFiles() const
|
||||
{
|
||||
QgsStringMap files;
|
||||
QString dir = mArchive->dir();
|
||||
const QStringList tempFiles = mArchive->files();
|
||||
for ( const QString &tempFile : tempFiles )
|
||||
files.insert( tempFile, dir + QDir::separator() + tempFile );
|
||||
return files;
|
||||
}
|
||||
|
||||
const QgsProjectMetadata &QgsProject::metadata() const
|
||||
{
|
||||
return mMetadata;
|
||||
|
@ -966,6 +966,25 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
|
||||
*/
|
||||
QgsAuxiliaryStorage *auxiliaryStorage();
|
||||
|
||||
/**
|
||||
* Returns the path to an attached file known by \a fileName.
|
||||
*
|
||||
* \note Attached files are only supported by QGZ file based projects
|
||||
* \see collectAttachedFiles()
|
||||
* \since QGIS 3.8
|
||||
*/
|
||||
QString attachedFile( const QString &fileName ) const;
|
||||
|
||||
/**
|
||||
* Returns a map of all attached files with relative paths and real paths.
|
||||
*
|
||||
* \note Attached files are only supported by QGZ file based projects
|
||||
* \see collectAttachedFiles()
|
||||
* \see attachedFile()
|
||||
* \since QGIS 3.8
|
||||
*/
|
||||
QgsStringMap attachedFiles() const;
|
||||
|
||||
/**
|
||||
* Returns a reference to the project's metadata store.
|
||||
* \see setMetadata()
|
||||
@ -1329,6 +1348,26 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
|
||||
*/
|
||||
void isDirtyChanged( bool dirty );
|
||||
|
||||
/**
|
||||
* Emitted whenever the project is saved to a qgz file.
|
||||
* This can be used to package additional files into the qgz file by modifying the \a files map.
|
||||
*
|
||||
* Map keys represent relative paths inside the qgz file, map values represent the path to
|
||||
* the source file.
|
||||
*
|
||||
* In python, append additional files to the map and return the modified map.
|
||||
*
|
||||
* \code{.py}
|
||||
* QgsProject.instance().collectAttachedFiles.connect(lambda files: files + ['/absolute/path/to/my/attachment.txt'])
|
||||
* \endcode
|
||||
*
|
||||
* \note Only will be emitted with QGZ project files
|
||||
* \see attachedFiles()
|
||||
* \see attachedFile()
|
||||
* \since QGIS 3.8
|
||||
*/
|
||||
void collectAttachedFiles( QgsStringMap &files SIP_INOUT );
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
|
@ -82,6 +82,9 @@ bool QgsZipUtils::unzip( const QString &zipFilename, const QString &dir, QString
|
||||
if ( zip_fread( file, buf.get(), len ) != -1 )
|
||||
{
|
||||
QString fileName( stat.name );
|
||||
// remove leading `/` e.g. `/project.qgs` -> `project.qgs`
|
||||
while ( fileName.startsWith( QDir::separator() ) )
|
||||
fileName.remove( 0, 1 );
|
||||
QFileInfo newFile( QDir( dir ), fileName );
|
||||
|
||||
// Create path for a new file if it does not exist.
|
||||
@ -129,7 +132,7 @@ bool QgsZipUtils::unzip( const QString &zipFilename, const QString &dir, QString
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files )
|
||||
bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files, const QString &root )
|
||||
{
|
||||
if ( zipFilename.isEmpty() )
|
||||
{
|
||||
@ -153,15 +156,22 @@ bool QgsZipUtils::zip( const QString &zipFilename, const QStringList &files )
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray fileNamePtr = file.toUtf8();
|
||||
zip_source *src = zip_source_file( z, fileNamePtr.constData(), 0, 0 );
|
||||
const QByteArray filePathUtf8 = file.toUtf8();
|
||||
zip_source *src = zip_source_file( z, filePathUtf8.constData(), 0, 0 );
|
||||
if ( src )
|
||||
{
|
||||
const QByteArray fileInfoPtr = fileInfo.fileName().toUtf8();
|
||||
QString fileName;
|
||||
if ( root.isEmpty() || !file.startsWith( root ) )
|
||||
fileName = fileInfo.fileName();
|
||||
else
|
||||
fileName = file.right( file.length() - root.length() );
|
||||
|
||||
|
||||
const QByteArray fileNameUtf8 = fileName.toUtf8();
|
||||
#if LIBZIP_VERSION_MAJOR < 1
|
||||
int rc = ( int ) zip_add( z, fileInfoPtr.constData(), src );
|
||||
int rc = ( int ) zip_add( z, fileNameUtf8.constData(), src );
|
||||
#else
|
||||
int rc = ( int ) zip_file_add( z, fileInfoPtr.constData(), src, 0 );
|
||||
int rc = ( int ) zip_file_add( z, fileNameUtf8.constData(), src, 0 );
|
||||
#endif
|
||||
if ( rc == -1 )
|
||||
{
|
||||
|
@ -54,9 +54,10 @@ namespace QgsZipUtils
|
||||
* also returned.
|
||||
* \param zip The zip filename
|
||||
* \param files The absolute path to files to embed within the zip
|
||||
* \param root The root path in which the files are located. This is used to determine the relative path inside the zip file.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
CORE_EXPORT bool zip( const QString &zip, const QStringList &files );
|
||||
CORE_EXPORT bool zip( const QString &zip, const QStringList &files, const QString &root = QString() );
|
||||
};
|
||||
|
||||
#endif //QGSZIPUTILS_H
|
||||
|
@ -43,6 +43,7 @@ class TestQgsProject : public QObject
|
||||
void testLayerFlags();
|
||||
void testLocalFiles();
|
||||
void testLocalUrlFiles();
|
||||
void testAttachedFiles();
|
||||
};
|
||||
|
||||
void TestQgsProject::init()
|
||||
@ -404,7 +405,6 @@ void TestQgsProject::testLocalFiles()
|
||||
f2.close();
|
||||
QgsPathResolver resolver( f.fileName( ) );
|
||||
QCOMPARE( resolver.writePath( layerPath ), QString( "./" + info.baseName() + ".shp" ) ) ;
|
||||
|
||||
}
|
||||
|
||||
void TestQgsProject::testLocalUrlFiles()
|
||||
@ -427,6 +427,30 @@ void TestQgsProject::testLocalUrlFiles()
|
||||
|
||||
}
|
||||
|
||||
void TestQgsProject::testAttachedFiles()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
QString qgzFileName = dir.filePath( QStringLiteral( "project.qgz" ) );
|
||||
|
||||
QTemporaryFile attachedFile;
|
||||
|
||||
if ( attachedFile.open() )
|
||||
{
|
||||
QTextStream stream( &attachedFile );
|
||||
stream << QStringLiteral( "success" ) << endl;
|
||||
}
|
||||
|
||||
QgsProject prj;
|
||||
connect( &prj, &QgsProject::collectAttachedFiles, this, [ &attachedFile ]( QgsStringMap & files ) { files.insert( QStringLiteral( "test/file.txt" ), attachedFile.fileName() ); } );
|
||||
prj.write( qgzFileName );
|
||||
prj.clear();
|
||||
prj.read( qgzFileName );
|
||||
QFile extractedFile( prj.attachedFile( QStringLiteral( "test/file.txt" ) ) );
|
||||
extractedFile.open( QFile::ReadOnly | QFile::Text );
|
||||
QTextStream in( &extractedFile );
|
||||
QCOMPARE( QString::fromUtf8( extractedFile.readAll().constData() ), QStringLiteral( "success\n" ) );
|
||||
}
|
||||
|
||||
|
||||
QGSTEST_MAIN( TestQgsProject )
|
||||
#include "testqgsproject.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user