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 }