From 43a3756e1c77b7e4ffebb26bdd024613fa1f4e18 Mon Sep 17 00:00:00 2001 From: Fabio D'Urso Date: Sat, 10 May 2014 00:53:39 +0200 Subject: [PATCH] Hot-swap backing file instead of reloading (if the generator supports it) --- core/document.cpp | 70 ++++++++++++++++++++++++++ core/document.h | 35 +++++++++++++ core/generator.cpp | 5 ++ core/generator.h | 14 +++++- generators/kimgio/generator_kimgio.cpp | 8 +++ generators/kimgio/generator_kimgio.h | 1 + part.cpp | 58 ++++++++++++++++++++- part.h | 1 + 8 files changed, 189 insertions(+), 3 deletions(-) diff --git a/core/document.cpp b/core/document.cpp index d458a4c2c..c41d047e1 100644 --- a/core/document.cpp +++ b/core/document.cpp @@ -3998,6 +3998,76 @@ const KComponentData* Document::componentData() const return kcd; } +bool Document::canSwapBackingFile() const +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + return genIt->generator->hasFeature( Generator::SwapBackingFile ); +} + +bool Document::swapBackingFile( const QString &newFileName, const KUrl & url ) +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + if ( !genIt->generator->hasFeature( Generator::SwapBackingFile ) ) + return false; + + kDebug(OkularDebug) << "Swapping backing file to" << newFileName; + if (genIt->generator->swapBackingFile( newFileName )) + { + d->m_url = url; + return true; + } + else + { + return false; + } +} + +bool Document::swapBackingFileArchive( const QString &newFileName, const KUrl & url ) +{ + if ( !d->m_generator ) + return false; + Q_ASSERT( !d->m_generatorName.isEmpty() ); + + QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); + Q_ASSERT( genIt != d->m_loadedGenerators.end() ); + + if ( !genIt->generator->hasFeature( Generator::SwapBackingFile ) ) + return false; + + kDebug(OkularDebug) << "Swapping backing archive to" << newFileName; + + ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive( newFileName ); + if ( !newArchive ) + return false; + + const QString tempFileName = newArchive->document.fileName(); + + kDebug(OkularDebug) << "Swapping backing file to" << tempFileName; + if (genIt->generator->swapBackingFile( tempFileName )) + { + delete d->m_archiveData; + d->m_archiveData = newArchive; + d->m_url = url; + return true; + } + else + { + return false; + } +} + bool Document::canSaveChanges() const { if ( !d->m_generator ) diff --git a/core/document.h b/core/document.h index 585004960..58c2c4b45 100644 --- a/core/document.h +++ b/core/document.h @@ -613,6 +613,41 @@ class OKULAR_EXPORT Document : public QObject */ const KComponentData* componentData() const; + /** + * Returns whether the generator supports hot-swapping the current file + * with another identical file + * + * @since 0.20 (KDE 4.14) + */ + bool canSwapBackingFile() const; + + /** + * Reload the document from a new location, without any visible effect + * to the user. + * + * The new file must be identical to the current one or, if the document + * has been modified (eg the user edited forms and annotations), the new + * document must have these changes too. For example, you can call + * saveChanges first to write changes to a file and then swapBackingFile + * to switch to the new location. + * + * @since 0.20 (KDE 4.14) + */ + bool swapBackingFile( const QString &newFileName, const KUrl & url ); + + /** + * Same as swapBackingFile, but newFileName must be a .okular file. + * + * The new file must be identical to the current one or, if the document + * has been modified (eg the user edited forms and annotations), the new + * document must have these changes too. For example, you can call + * saveDocumentArchive first to write changes to a file and then + * swapBackingFileArchive to switch to the new location. + * + * @since 0.20 (KDE 4.14) + */ + bool swapBackingFileArchive( const QString &newFileName, const KUrl & url ); + /** * Saving capabilities. Their availability varies according to the * underlying generator and/or the document type. diff --git a/core/generator.cpp b/core/generator.cpp index ec1d1f76d..704bf4d66 100644 --- a/core/generator.cpp +++ b/core/generator.cpp @@ -196,6 +196,11 @@ Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArr return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } +bool Generator::swapBackingFile( QString const &newFileName ) +{ + return false; +} + bool Generator::closeDocument() { Q_D( Generator ); diff --git a/core/generator.h b/core/generator.h index 506f8a826..9784dadb0 100644 --- a/core/generator.h +++ b/core/generator.h @@ -207,7 +207,8 @@ class OKULAR_EXPORT Generator : public QObject PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based). PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog - TiledRendering ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) + TiledRendering, ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) + SwapBackingFile ///< Whether the Generator can hot-swap the file it's reading from @since 0.20 (KDE 4.14) }; /** @@ -268,6 +269,17 @@ class OKULAR_EXPORT Generator : public QObject */ virtual Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString &password ); + /** + * Changes the path of the file we are reading from. The new path must + * point to a copy of the same document. + * + * @note the Generator has to have the feature @ref SwapBackingFile enabled + * + * @since 0.20 (KDE 4.14) + * + * @returns true on success, false otherwise. + */ + virtual bool swapBackingFile( const QString &newFileName ); /** * This method is called when the document is closed and not used diff --git a/generators/kimgio/generator_kimgio.cpp b/generators/kimgio/generator_kimgio.cpp index f856434fe..c6822415a 100644 --- a/generators/kimgio/generator_kimgio.cpp +++ b/generators/kimgio/generator_kimgio.cpp @@ -56,6 +56,7 @@ KIMGIOGenerator::KIMGIOGenerator( QObject *parent, const QVariantList &args ) setFeature( TiledRendering ); setFeature( PrintNative ); setFeature( PrintToFile ); + setFeature( SwapBackingFile ); /* setComponentData( *ownComponentData() ); @@ -130,6 +131,13 @@ bool KIMGIOGenerator::loadDocumentFromData( const QByteArray & fileData, QVector return true; } +bool KIMGIOGenerator::swapBackingFile( QString const &newFileName ) +{ + // NOP: We don't actually need to do anything because all data has already + // been loaded in RAM + return true; +} + bool KIMGIOGenerator::doCloseDocument() { m_img = QImage(); diff --git a/generators/kimgio/generator_kimgio.h b/generators/kimgio/generator_kimgio.h index faebd8572..a60c36e72 100644 --- a/generators/kimgio/generator_kimgio.h +++ b/generators/kimgio/generator_kimgio.h @@ -25,6 +25,7 @@ class KIMGIOGenerator : public Okular::Generator // [INHERITED] load a document and fill up the pagesVector bool loadDocument( const QString & fileName, QVector & pagesVector ); bool loadDocumentFromData( const QByteArray & fileData, QVector & pagesVector ); + bool swapBackingFile( QString const &newFileName ); // [INHERITED] print document using already configured kprinter bool print( QPrinter& printer ); diff --git a/part.cpp b/part.cpp index 620530cdc..3ab912317 100644 --- a/part.cpp +++ b/part.cpp @@ -303,7 +303,7 @@ QObject *parent, const QVariantList &args, KComponentData componentData ) : KParts::ReadWritePart(parent), -m_tempfile( 0 ), m_fileWasRemoved( false ), m_showMenuBarAction( 0 ), m_showFullScreenAction( 0 ), m_actionsSearched( false ), +m_tempfile( 0 ), m_swapInsteadOfOpening( false ), m_fileWasRemoved( false ), m_showMenuBarAction( 0 ), m_showFullScreenAction( 0 ), m_actionsSearched( false ), m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentWidget, parent, args)), m_generatorGuiClient(0), m_keeper( 0 ) { // first, we check if a config file name has been specified @@ -1265,6 +1265,26 @@ bool Part::openFile() uncompressOk = handleCompressed( fileNameToOpen, localFilePath(), compressedMime ); mime = KMimeType::findByPath( fileNameToOpen ); } + + if ( m_swapInsteadOfOpening ) + { + m_swapInsteadOfOpening = false; + + if ( !uncompressOk ) + return false; + + if ( mime->is( "application/vnd.kde.okular-archive" ) ) + { + isDocumentArchive = true; + return m_document->swapBackingFileArchive( fileNameToOpen, url() ); + } + else + { + isDocumentArchive = false; + return m_document->swapBackingFile( fileNameToOpen, url() ); + } + } + Document::OpenResult openResult = Document::OpenError; isDocumentArchive = false; if ( uncompressOk ) @@ -1542,6 +1562,13 @@ bool Part::closeUrl(bool promptToSave) if ( promptToSave && !queryClose() ) return false; + if ( m_swapInsteadOfOpening ) + { + // If we're swapping the backing file, we don't want to close the + // current one when openUrl() calls us internally + return true; // pretend it worked + } + setModified( false ); if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath()) @@ -2396,7 +2423,34 @@ bool Part::saveAs( const KUrl & saveUrl, bool saveAsOkularArchive ) // Load new file instead of the old one if ( url() != saveUrl ) - slotAttemptReload( true, saveUrl ); + { + if ( m_document->canSwapBackingFile() ) + { + // If the generator supports hot-swapping of the backing file + // tell openFile to swap the backing file instead of opening a new one + m_swapInsteadOfOpening = true; + + // this calls openFile internally, which in turn actually calls + // m_document->swapBackingFile() instead of the regular loadDocument + const bool success = openUrl( saveUrl ); + + // restore it back to false -- this has already been done by + // openFile, but let's do it again for extra safety + m_swapInsteadOfOpening = false; + + // In case of file swapping errors, close everything to avoid inconsistencies + if ( !success ) + { + closeUrl(); + } + } + else + { + // If the generator doesn't support swapping file, then just reload + // the document from the new location + slotAttemptReload( true, saveUrl ); + } + } // Restore watcher if ( url().isLocalFile() ) diff --git a/part.h b/part.h index 19b432031..4facd1f29 100644 --- a/part.h +++ b/part.h @@ -259,6 +259,7 @@ class OKULAR_PART_EXPORT Part : public KParts::ReadWritePart, public Okular::Doc Okular::Document * m_document; QString m_temporaryLocalFile; bool isDocumentArchive; + bool m_swapInsteadOfOpening; // if set, the next open operation will replace the backing file (used when reloading just saved files) // main widgets Sidebar *m_sidebar;