Warning, file /office/calligra/libs/main/KoFilterChain.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the Calligra libraries
0002    Copyright (C) 2001 Werner Trobin <trobin@kde.org>
0003 
0004 This library is free software; you can redistribute it and/or
0005 modify it under the terms of the GNU Library General Public
0006 License as published by the Free Software Foundation; either
0007 version 2 of the License, or (at your option) any later version.
0008 
0009 This library is distributed in the hope that it will be useful,
0010 but WITHOUT ANY WARRANTY; without even the implied warranty of
0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012 Library General Public License for more details.
0013 
0014 You should have received a copy of the GNU Library General Public License
0015 along with this library; see the file COPYING.LIB.  If not, write to
0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017 Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KoFilterChain.h"
0021 
0022 #include "KoFilterManager.h"  // KoFilterManager::filterAvailable, private API
0023 #include "KoDocumentEntry.h"
0024 #include "KoFilterEntry.h"
0025 #include "KoDocument.h"
0026 #include "KoPart.h"
0027 
0028 #include "PriorityQueue_p.h"
0029 #include "KoFilterGraph.h"
0030 #include "KoFilterEdge.h"
0031 #include "KoFilterChainLink.h"
0032 #include "KoFilterVertex.h"
0033 
0034 #include <QMetaMethod>
0035 #include <QTemporaryFile>
0036 #include <QMimeDatabase>
0037 
0038 #include <MainDebug.h>
0039 
0040 #include <limits.h> // UINT_MAX
0041 
0042 // Those "defines" are needed in the setupConnections method below.
0043 // Please always keep the strings and the length in sync!
0044 using namespace CalligraFilter;
0045 
0046 
0047 KoFilterChain::KoFilterChain(const KoFilterManager* manager) :
0048         m_manager(manager), m_state(Beginning), m_inputStorage(0),
0049         m_inputStorageDevice(0), m_outputStorage(0), m_outputStorageDevice(0),
0050         m_inputDocument(0), m_outputDocument(0), m_inputTempFile(0),
0051         m_outputTempFile(0), m_inputQueried(Nil), m_outputQueried(Nil), d(0)
0052 {
0053 }
0054 
0055 
0056 KoFilterChain::~KoFilterChain()
0057 {
0058     m_chainLinks.deleteAll();
0059 
0060     if (filterManagerParentChain() && filterManagerParentChain()->m_outputStorage)
0061         filterManagerParentChain()->m_outputStorage->leaveDirectory();
0062     manageIO(); // Called for the 2nd time in a row -> clean up
0063 }
0064 
0065 KoFilter::ConversionStatus KoFilterChain::invokeChain()
0066 {
0067     KoFilter::ConversionStatus status = KoFilter::OK;
0068 
0069     m_state = Beginning;
0070     int count = m_chainLinks.count();
0071 
0072     // This is needed due to nasty Microsoft design
0073     const ChainLink* parentChainLink = 0;
0074     if (filterManagerParentChain())
0075         parentChainLink = filterManagerParentChain()->m_chainLinks.current();
0076 
0077     // No iterator here, as we need m_chainLinks.current() in outputDocument()
0078     m_chainLinks.first();
0079     for (; count > 1 && m_chainLinks.current() && status == KoFilter::OK;
0080             m_chainLinks.next(), --count) {
0081         status = m_chainLinks.current()->invokeFilter(parentChainLink);
0082         m_state = Middle;
0083         manageIO();
0084     }
0085 
0086     if (!m_chainLinks.current()) {
0087         warnFilter << "Huh?? Found a null pointer in the chain";
0088         return KoFilter::StupidError;
0089     }
0090 
0091     if (status == KoFilter::OK) {
0092         if (m_state & Beginning)
0093             m_state |= End;
0094         else
0095             m_state = End;
0096         status = m_chainLinks.current()->invokeFilter(parentChainLink);
0097         manageIO();
0098     }
0099 
0100     m_state = Done;
0101     if (status == KoFilter::OK)
0102         finalizeIO();
0103     return status;
0104 }
0105 
0106 QString KoFilterChain::chainOutput() const
0107 {
0108     if (m_state == Done)
0109         return m_inputFile; // as we already called manageIO()
0110     return QString();
0111 }
0112 
0113 QString KoFilterChain::inputFile()
0114 {
0115     if (m_inputQueried == File)
0116         return m_inputFile;
0117     else if (m_inputQueried != Nil) {
0118         warnFilter << "You already asked for some different source.";
0119         return QString();
0120     }
0121     m_inputQueried = File;
0122 
0123     if (m_state & Beginning) {
0124         if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
0125                 KoFilterManager::Import)
0126             m_inputFile = filterManagerImportFile();
0127         else
0128             inputFileHelper(filterManagerKoDocument(), filterManagerImportFile());
0129     } else
0130         if (m_inputFile.isEmpty())
0131             inputFileHelper(m_inputDocument, QString());
0132 
0133     return m_inputFile;
0134 }
0135 
0136 QString KoFilterChain::outputFile()
0137 {
0138     // sanity check: No embedded filter should ask for a plain file
0139     // ###### CHECK: This will break as soon as we support exporting embedding filters
0140     if (filterManagerParentChain())
0141         warnFilter << "An embedded filter has to use storageFile()!";
0142 
0143     if (m_outputQueried == File)
0144         return m_outputFile;
0145     else if (m_outputQueried != Nil) {
0146         warnFilter << "You already asked for some different destination.";
0147         return QString();
0148     }
0149     m_outputQueried = File;
0150 
0151     if (m_state & End) {
0152         if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
0153                 KoFilterManager::Import)
0154             outputFileHelper(false);    // This (last) one gets deleted by the caller
0155         else
0156             m_outputFile = filterManagerExportFile();
0157     } else
0158         outputFileHelper(true);
0159 
0160     return m_outputFile;
0161 }
0162 
0163 KoStoreDevice* KoFilterChain::storageFile(const QString& name, KoStore::Mode mode)
0164 {
0165     // Plain normal use case
0166     if (m_inputQueried == Storage && mode == KoStore::Read &&
0167             m_inputStorage && m_inputStorage->mode() == KoStore::Read)
0168         return storageNewStreamHelper(&m_inputStorage, &m_inputStorageDevice, name);
0169     else if (m_outputQueried == Storage && mode == KoStore::Write &&
0170              m_outputStorage && m_outputStorage->mode() == KoStore::Write)
0171         return storageNewStreamHelper(&m_outputStorage, &m_outputStorageDevice, name);
0172     else if (m_inputQueried == Nil && mode == KoStore::Read)
0173         return storageHelper(inputFile(), name, KoStore::Read,
0174                              &m_inputStorage, &m_inputStorageDevice);
0175     else if (m_outputQueried == Nil && mode == KoStore::Write)
0176         return storageHelper(outputFile(), name, KoStore::Write,
0177                              &m_outputStorage, &m_outputStorageDevice);
0178     else {
0179         warnFilter << "Oooops, how did we get here? You already asked for a"
0180         << " different source/destination?" << endl;
0181         return 0;
0182     }
0183 }
0184 
0185 KoDocument* KoFilterChain::inputDocument()
0186 {
0187     if (m_inputQueried == Document)
0188         return m_inputDocument;
0189     else if (m_inputQueried != Nil) {
0190         warnFilter << "You already asked for some different source.";
0191         return 0;
0192     }
0193 
0194     if ((m_state & Beginning) &&
0195             static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export &&
0196             filterManagerKoDocument())
0197         m_inputDocument = filterManagerKoDocument();
0198     else if (!m_inputDocument)
0199         m_inputDocument = createDocument(inputFile());
0200 
0201     m_inputQueried = Document;
0202     return m_inputDocument;
0203 }
0204 
0205 KoDocument* KoFilterChain::outputDocument()
0206 {
0207     // sanity check: No embedded filter should ask for a document
0208     // ###### CHECK: This will break as soon as we support exporting embedding filters
0209     if (filterManagerParentChain()) {
0210         warnFilter << "An embedded filter has to use storageFile()!";
0211         return 0;
0212     }
0213 
0214     if (m_outputQueried == Document)
0215         return m_outputDocument;
0216     else if (m_outputQueried != Nil) {
0217         warnFilter << "You already asked for some different destination.";
0218         return 0;
0219     }
0220 
0221     if ((m_state & End) &&
0222             static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Import &&
0223             filterManagerKoDocument())
0224         m_outputDocument = filterManagerKoDocument();
0225     else
0226         m_outputDocument = createDocument(m_chainLinks.current()->to());
0227 
0228     m_outputQueried = Document;
0229     return m_outputDocument;
0230 }
0231 
0232 void KoFilterChain::dump()
0233 {
0234     debugFilter << "########## KoFilterChain with" << m_chainLinks.count() << " members:";
0235     ChainLink* link = m_chainLinks.first();
0236     while (link) {
0237         link->dump();
0238         link = m_chainLinks.next();
0239     }
0240     debugFilter << "########## KoFilterChain (done) ##########";
0241 }
0242 
0243 void KoFilterChain::appendChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
0244 {
0245     m_chainLinks.append(new ChainLink(this, filterEntry, from, to));
0246 }
0247 
0248 void KoFilterChain::prependChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
0249 {
0250     m_chainLinks.prepend(new ChainLink(this, filterEntry, from, to));
0251 }
0252 
0253 QString KoFilterChain::filterManagerImportFile() const
0254 {
0255     return m_manager->importFile();
0256 }
0257 
0258 QString KoFilterChain::filterManagerExportFile() const
0259 {
0260     return m_manager->exportFile();
0261 }
0262 
0263 KoDocument* KoFilterChain::filterManagerKoDocument() const
0264 {
0265     return m_manager->document();
0266 }
0267 
0268 int KoFilterChain::filterManagerDirection() const
0269 {
0270     return m_manager->direction();
0271 }
0272 
0273 KoFilterChain* KoFilterChain::filterManagerParentChain() const
0274 {
0275     return m_manager->parentChain();
0276 }
0277 
0278 void KoFilterChain::manageIO()
0279 {
0280     m_inputQueried = Nil;
0281     m_outputQueried = Nil;
0282 
0283     delete m_inputStorageDevice;
0284     m_inputStorageDevice = 0;
0285     if (m_inputStorage) {
0286         m_inputStorage->close();
0287         delete m_inputStorage;
0288         m_inputStorage = 0;
0289     }
0290     delete m_inputTempFile;  // autodelete
0291     m_inputTempFile = 0;
0292     m_inputFile.clear();
0293 
0294     if (!m_outputFile.isEmpty()) {
0295         if (m_outputTempFile == 0) {
0296             m_inputTempFile = new QTemporaryFile;
0297             m_inputTempFile->setAutoRemove(true);
0298             m_inputTempFile->setFileName(m_outputFile);
0299         }
0300         else {
0301             m_inputTempFile = m_outputTempFile;
0302             m_outputTempFile = 0;
0303         }
0304         m_inputFile = m_outputFile;
0305         m_outputFile.clear();
0306         m_inputTempFile = m_outputTempFile;
0307         m_outputTempFile = 0;
0308 
0309         delete m_outputStorageDevice;
0310         m_outputStorageDevice = 0;
0311         if (m_outputStorage) {
0312             m_outputStorage->close();
0313             // Don't delete the storage if we're just pointing to the
0314             // storage of the parent filter chain
0315             if (!filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write)
0316                 delete m_outputStorage;
0317             m_outputStorage = 0;
0318         }
0319     }
0320 
0321     if (m_inputDocument != filterManagerKoDocument())
0322         delete m_inputDocument;
0323     m_inputDocument = m_outputDocument;
0324     m_outputDocument = 0;
0325 }
0326 
0327 void KoFilterChain::finalizeIO()
0328 {
0329     // In case we export (to a file, of course) and the last
0330     // filter chose to output a KoDocument we have to save it.
0331     // Should be very rare, but well...
0332     // Note: m_*input*Document as we already called manageIO()
0333     if (m_inputDocument &&
0334             static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export) {
0335         debugFilter << "Saving the output document to the export file " << m_chainLinks.current()->to();
0336         m_inputDocument->setOutputMimeType(m_chainLinks.current()->to());
0337         m_inputDocument->saveNativeFormat(filterManagerExportFile());
0338         m_inputFile = filterManagerExportFile();
0339     }
0340 }
0341 
0342 bool KoFilterChain::createTempFile(QTemporaryFile** tempFile, bool autoDelete)
0343 {
0344     if (*tempFile) {
0345         errorFilter << "Ooops, why is there already a temp file???" << endl;
0346         return false;
0347     }
0348     *tempFile = new QTemporaryFile();
0349     (*tempFile)->setAutoRemove(autoDelete);
0350     return (*tempFile)->open();
0351 }
0352 
0353 /*  Note about Windows & usage of QTemporaryFile
0354 
0355     The QTemporaryFile objects m_inputTempFile and m_outputTempFile are just used
0356     to reserve a temporary file with a unique name which then can be used to store
0357     an intermediate format. The filters themselves do not get access to these objects,
0358     but can query KoFilterChain only for the filename and then have to open the files
0359     themselves with their own file handlers (TODO: change this).
0360     On Windows this seems to be a problem and results in content not sync'ed to disk etc.
0361 
0362     So unless someone finds out which flags might be needed on opening the files on
0363     Windows to prevent this behaviour (unless these details are hidden away by the
0364     Qt abstraction and cannot be influenced), a workaround is to destruct the
0365     QTemporaryFile objects right after creation again and just take the name,
0366     to avoid having two file handlers on the same file.
0367 
0368     A better fix might be to use the QTemporaryFile objects also by the filters,
0369     instead of having them open the same file on their own again, but that needs more work
0370     and is left for... you :)
0371 */
0372 
0373 void KoFilterChain::inputFileHelper(KoDocument* document, const QString& alternativeFile)
0374 {
0375     if (document) {
0376         if (!createTempFile(&m_inputTempFile)) {
0377             delete m_inputTempFile;
0378             m_inputTempFile = 0;
0379             m_inputFile.clear();
0380             return;
0381         }
0382         m_inputFile = m_inputTempFile->fileName();
0383         // See "Note about Windows & usage of QTemporaryFile" above
0384 #ifdef Q_OS_WIN
0385         m_inputTempFile->close();
0386         m_inputTempFile->setAutoRemove(true);
0387         delete m_inputTempFile;
0388         m_inputTempFile = 0;
0389 #endif
0390         document->setOutputMimeType(m_chainLinks.current()->from());
0391         if (!document->saveNativeFormat(m_inputFile)) {
0392             delete m_inputTempFile;
0393             m_inputTempFile = 0;
0394             m_inputFile.clear();
0395             return;
0396         }
0397     } else
0398         m_inputFile = alternativeFile;
0399 }
0400 
0401 void KoFilterChain::outputFileHelper(bool autoDelete)
0402 {
0403     if (!createTempFile(&m_outputTempFile, autoDelete)) {
0404         delete m_outputTempFile;
0405         m_outputTempFile = 0;
0406         m_outputFile.clear();
0407     } else {
0408         m_outputFile = m_outputTempFile->fileName();
0409 
0410         // See "Note about Windows & usage of QTemporaryFile" above
0411 #ifdef Q_OS_WIN
0412         m_outputTempFile->close();
0413         m_outputTempFile->setAutoRemove(true);
0414         delete m_outputTempFile;
0415         m_outputTempFile = 0;
0416 #endif
0417     }
0418 }
0419 
0420 KoStoreDevice* KoFilterChain::storageNewStreamHelper(KoStore** storage, KoStoreDevice** device,
0421         const QString& name)
0422 {
0423     delete *device;
0424     *device = 0;
0425     if ((*storage)->isOpen())
0426         (*storage)->close();
0427     if ((*storage)->bad())
0428         return storageCleanupHelper(storage);
0429     if (!(*storage)->open(name))
0430         return 0;
0431 
0432     *device = new KoStoreDevice(*storage);
0433     return *device;
0434 }
0435 
0436 KoStoreDevice* KoFilterChain::storageHelper(const QString& file, const QString& streamName,
0437         KoStore::Mode mode, KoStore** storage,
0438         KoStoreDevice** device)
0439 {
0440     if (file.isEmpty())
0441         return 0;
0442     if (*storage) {
0443         debugFilter << "Uh-oh, we forgot to clean up...";
0444         return 0;
0445     }
0446 
0447     storageInit(file, mode, storage);
0448 
0449     if ((*storage)->bad())
0450         return storageCleanupHelper(storage);
0451 
0452     // Seems that we got a valid storage, at least. Even if we can't open
0453     // the stream the "user" asked us to open, we nonetheless change the
0454     // IOState from File to Storage, as it might be possible to open other streams
0455     if (mode == KoStore::Read)
0456         m_inputQueried = Storage;
0457     else // KoStore::Write
0458         m_outputQueried = Storage;
0459 
0460     return storageCreateFirstStream(streamName, storage, device);
0461 }
0462 
0463 void KoFilterChain::storageInit(const QString& file, KoStore::Mode mode, KoStore** storage)
0464 {
0465     QByteArray appIdentification("");
0466     if (mode == KoStore::Write) {
0467         // To create valid storages we also have to add the mimetype
0468         // magic "applicationIndentifier" to the storage.
0469         // As only filters with a Calligra destination should query
0470         // for a storage to write to, we don't check the content of
0471         // the mimetype here. It doesn't do a lot of harm if someone
0472         // "abuses" this method.
0473         appIdentification = m_chainLinks.current()->to();
0474     }
0475     *storage = KoStore::createStore(file, mode, appIdentification);
0476 }
0477 
0478 KoStoreDevice* KoFilterChain::storageCreateFirstStream(const QString& streamName, KoStore** storage,
0479         KoStoreDevice** device)
0480 {
0481     if (!(*storage)->open(streamName))
0482         return 0;
0483 
0484     if (*device) {
0485         debugFilter << "Uh-oh, we forgot to clean up the storage device!";
0486         (*storage)->close();
0487         return storageCleanupHelper(storage);
0488     }
0489     *device = new KoStoreDevice(*storage);
0490     return *device;
0491 }
0492 
0493 KoStoreDevice* KoFilterChain::storageCleanupHelper(KoStore** storage)
0494 {
0495     // Take care not to delete the storage of the parent chain
0496     if (*storage != m_outputStorage || !filterManagerParentChain() ||
0497             (*storage)->mode() != KoStore::Write)
0498         delete *storage;
0499     *storage = 0;
0500     return 0;
0501 }
0502 
0503 KoDocument* KoFilterChain::createDocument(const QString& file)
0504 {
0505     QUrl url;
0506     url.setPath(file);
0507     QMimeType t = QMimeDatabase().mimeTypeForUrl(url);
0508     if (t.isDefault()) {
0509         errorFilter << "No mimetype found for " << file << endl;
0510         return 0;
0511     }
0512 
0513     KoDocument *doc = createDocument(t.name().toLatin1());
0514 
0515     if (!doc || !doc->loadNativeFormat(file)) {
0516         errorFilter << "Couldn't load from the file" << endl;
0517         delete doc;
0518         return 0;
0519     }
0520     return doc;
0521 }
0522 
0523 KoDocument* KoFilterChain::createDocument(const QByteArray& mimeType)
0524 {
0525     KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(mimeType);
0526 
0527     if (entry.isEmpty()) {
0528         errorFilter << "Couldn't find a part that can handle mimetype " << mimeType << endl;
0529     }
0530 
0531     QString errorMsg;
0532     KoPart *part = entry.createKoPart(&errorMsg);
0533     if (!part) {
0534         errorFilter << "Couldn't create the document: " << errorMsg << endl;
0535         return 0;
0536     }
0537     return part->document();
0538 }
0539 
0540 int KoFilterChain::weight() const
0541 {
0542     return m_chainLinks.count();
0543 }