1 /* This file is part of the Calligra libraries
2 Copyright (C) 2001 Werner Trobin <trobin@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18 */
19
20 // clazy:excludeall=qstring-arg
21 #include "KoFilterChain.h"
22
23 #include "KoFilterManager.h" // KoFilterManager::filterAvailable, private API
24 #include "KoDocumentEntry.h"
25 #include "KoFilterEntry.h"
26 #include "KoDocument.h"
27 #include "KoPart.h"
28
29 #include "PriorityQueue_p.h"
30 #include "KoFilterGraph.h"
31 #include "KoFilterEdge.h"
32 #include "KoFilterChainLink.h"
33 #include "KoFilterVertex.h"
34
35 #include <QMetaMethod>
36 #include <QTemporaryFile>
37 #include <QMimeDatabase>
38
39 #include <MainDebug.h>
40
41 #include <limits.h> // UINT_MAX
42
43 // Those "defines" are needed in the setupConnections method below.
44 // Please always keep the strings and the length in sync!
45 using namespace CalligraFilter;
46
47
KoFilterChain(const KoFilterManager * manager)48 KoFilterChain::KoFilterChain(const KoFilterManager* manager) :
49 m_manager(manager), m_state(Beginning), m_inputStorage(0),
50 m_inputStorageDevice(0), m_outputStorage(0), m_outputStorageDevice(0),
51 m_inputDocument(0), m_outputDocument(0), m_inputTempFile(0),
52 m_outputTempFile(0), m_inputQueried(Nil), m_outputQueried(Nil), d(0)
53 {
54 }
55
56
~KoFilterChain()57 KoFilterChain::~KoFilterChain()
58 {
59 m_chainLinks.deleteAll();
60
61 if (filterManagerParentChain() && filterManagerParentChain()->m_outputStorage)
62 filterManagerParentChain()->m_outputStorage->leaveDirectory();
63 manageIO(); // Called for the 2nd time in a row -> clean up
64 }
65
invokeChain()66 KoFilter::ConversionStatus KoFilterChain::invokeChain()
67 {
68 KoFilter::ConversionStatus status = KoFilter::OK;
69
70 m_state = Beginning;
71 int count = m_chainLinks.count();
72
73 // This is needed due to nasty Microsoft design
74 const ChainLink* parentChainLink = 0;
75 if (filterManagerParentChain())
76 parentChainLink = filterManagerParentChain()->m_chainLinks.current();
77
78 // No iterator here, as we need m_chainLinks.current() in outputDocument()
79 m_chainLinks.first();
80 for (; count > 1 && m_chainLinks.current() && status == KoFilter::OK;
81 m_chainLinks.next(), --count) {
82 status = m_chainLinks.current()->invokeFilter(parentChainLink);
83 m_state = Middle;
84 manageIO();
85 }
86
87 if (!m_chainLinks.current()) {
88 warnFilter << "Huh?? Found a null pointer in the chain";
89 return KoFilter::StupidError;
90 }
91
92 if (status == KoFilter::OK) {
93 if (m_state & Beginning)
94 m_state |= End;
95 else
96 m_state = End;
97 status = m_chainLinks.current()->invokeFilter(parentChainLink);
98 manageIO();
99 }
100
101 m_state = Done;
102 if (status == KoFilter::OK)
103 finalizeIO();
104 return status;
105 }
106
chainOutput() const107 QString KoFilterChain::chainOutput() const
108 {
109 if (m_state == Done)
110 return m_inputFile; // as we already called manageIO()
111 return QString();
112 }
113
inputFile()114 QString KoFilterChain::inputFile()
115 {
116 if (m_inputQueried == File)
117 return m_inputFile;
118 else if (m_inputQueried != Nil) {
119 warnFilter << "You already asked for some different source.";
120 return QString();
121 }
122 m_inputQueried = File;
123
124 if (m_state & Beginning) {
125 if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
126 KoFilterManager::Import)
127 m_inputFile = filterManagerImportFile();
128 else
129 inputFileHelper(filterManagerKoDocument(), filterManagerImportFile());
130 } else
131 if (m_inputFile.isEmpty())
132 inputFileHelper(m_inputDocument, QString());
133
134 return m_inputFile;
135 }
136
outputFile()137 QString KoFilterChain::outputFile()
138 {
139 // sanity check: No embedded filter should ask for a plain file
140 // ###### CHECK: This will break as soon as we support exporting embedding filters
141 if (filterManagerParentChain())
142 warnFilter << "An embedded filter has to use storageFile()!";
143
144 if (m_outputQueried == File)
145 return m_outputFile;
146 else if (m_outputQueried != Nil) {
147 warnFilter << "You already asked for some different destination.";
148 return QString();
149 }
150 m_outputQueried = File;
151
152 if (m_state & End) {
153 if (static_cast<KoFilterManager::Direction>(filterManagerDirection()) ==
154 KoFilterManager::Import)
155 outputFileHelper(false); // This (last) one gets deleted by the caller
156 else
157 m_outputFile = filterManagerExportFile();
158 } else
159 outputFileHelper(true);
160
161 return m_outputFile;
162 }
163
storageFile(const QString & name,KoStore::Mode mode)164 KoStoreDevice* KoFilterChain::storageFile(const QString& name, KoStore::Mode mode)
165 {
166 // Plain normal use case
167 if (m_inputQueried == Storage && mode == KoStore::Read &&
168 m_inputStorage && m_inputStorage->mode() == KoStore::Read)
169 return storageNewStreamHelper(&m_inputStorage, &m_inputStorageDevice, name);
170 else if (m_outputQueried == Storage && mode == KoStore::Write &&
171 m_outputStorage && m_outputStorage->mode() == KoStore::Write)
172 return storageNewStreamHelper(&m_outputStorage, &m_outputStorageDevice, name);
173 else if (m_inputQueried == Nil && mode == KoStore::Read)
174 return storageHelper(inputFile(), name, KoStore::Read,
175 &m_inputStorage, &m_inputStorageDevice);
176 else if (m_outputQueried == Nil && mode == KoStore::Write)
177 return storageHelper(outputFile(), name, KoStore::Write,
178 &m_outputStorage, &m_outputStorageDevice);
179 else {
180 warnFilter << "Oooops, how did we get here? You already asked for a"
181 << " different source/destination?" << endl;
182 return 0;
183 }
184 }
185
inputDocument()186 KoDocument* KoFilterChain::inputDocument()
187 {
188 if (m_inputQueried == Document)
189 return m_inputDocument;
190 else if (m_inputQueried != Nil) {
191 warnFilter << "You already asked for some different source.";
192 return 0;
193 }
194
195 if ((m_state & Beginning) &&
196 static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export &&
197 filterManagerKoDocument())
198 m_inputDocument = filterManagerKoDocument();
199 else if (!m_inputDocument)
200 m_inputDocument = createDocument(inputFile());
201
202 m_inputQueried = Document;
203 return m_inputDocument;
204 }
205
outputDocument()206 KoDocument* KoFilterChain::outputDocument()
207 {
208 // sanity check: No embedded filter should ask for a document
209 // ###### CHECK: This will break as soon as we support exporting embedding filters
210 if (filterManagerParentChain()) {
211 warnFilter << "An embedded filter has to use storageFile()!";
212 return 0;
213 }
214
215 if (m_outputQueried == Document)
216 return m_outputDocument;
217 else if (m_outputQueried != Nil) {
218 warnFilter << "You already asked for some different destination.";
219 return 0;
220 }
221
222 if ((m_state & End) &&
223 static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Import &&
224 filterManagerKoDocument())
225 m_outputDocument = filterManagerKoDocument();
226 else
227 m_outputDocument = createDocument(m_chainLinks.current()->to());
228
229 m_outputQueried = Document;
230 return m_outputDocument;
231 }
232
dump()233 void KoFilterChain::dump()
234 {
235 debugFilter << "########## KoFilterChain with" << m_chainLinks.count() << " members:";
236 ChainLink* link = m_chainLinks.first();
237 while (link) {
238 link->dump();
239 link = m_chainLinks.next();
240 }
241 debugFilter << "########## KoFilterChain (done) ##########";
242 }
243
appendChainLink(KoFilterEntry::Ptr filterEntry,const QByteArray & from,const QByteArray & to)244 void KoFilterChain::appendChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
245 {
246 m_chainLinks.append(new ChainLink(this, filterEntry, from, to));
247 }
248
prependChainLink(KoFilterEntry::Ptr filterEntry,const QByteArray & from,const QByteArray & to)249 void KoFilterChain::prependChainLink(KoFilterEntry::Ptr filterEntry, const QByteArray& from, const QByteArray& to)
250 {
251 m_chainLinks.prepend(new ChainLink(this, filterEntry, from, to));
252 }
253
filterManagerImportFile() const254 QString KoFilterChain::filterManagerImportFile() const
255 {
256 return m_manager->importFile();
257 }
258
filterManagerExportFile() const259 QString KoFilterChain::filterManagerExportFile() const
260 {
261 return m_manager->exportFile();
262 }
263
filterManagerKoDocument() const264 KoDocument* KoFilterChain::filterManagerKoDocument() const
265 {
266 return m_manager->document();
267 }
268
filterManagerDirection() const269 int KoFilterChain::filterManagerDirection() const
270 {
271 return m_manager->direction();
272 }
273
filterManagerParentChain() const274 KoFilterChain* KoFilterChain::filterManagerParentChain() const
275 {
276 return m_manager->parentChain();
277 }
278
manageIO()279 void KoFilterChain::manageIO()
280 {
281 m_inputQueried = Nil;
282 m_outputQueried = Nil;
283
284 delete m_inputStorageDevice;
285 m_inputStorageDevice = 0;
286 if (m_inputStorage) {
287 m_inputStorage->close();
288 delete m_inputStorage;
289 m_inputStorage = 0;
290 }
291 delete m_inputTempFile; // autodelete
292 m_inputTempFile = 0;
293 m_inputFile.clear();
294
295 if (!m_outputFile.isEmpty()) {
296 if (m_outputTempFile == 0) {
297 m_inputTempFile = new QTemporaryFile;
298 m_inputTempFile->setAutoRemove(true);
299 m_inputTempFile->setFileName(m_outputFile);
300 }
301 else {
302 m_inputTempFile = m_outputTempFile;
303 m_outputTempFile = 0;
304 }
305 m_inputFile = m_outputFile;
306 m_outputFile.clear();
307 m_inputTempFile = m_outputTempFile;
308 m_outputTempFile = 0;
309
310 delete m_outputStorageDevice;
311 m_outputStorageDevice = 0;
312 if (m_outputStorage) {
313 m_outputStorage->close();
314 // Don't delete the storage if we're just pointing to the
315 // storage of the parent filter chain
316 if (!filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write)
317 delete m_outputStorage;
318 m_outputStorage = 0;
319 }
320 }
321
322 if (m_inputDocument != filterManagerKoDocument())
323 delete m_inputDocument;
324 m_inputDocument = m_outputDocument;
325 m_outputDocument = 0;
326 }
327
finalizeIO()328 void KoFilterChain::finalizeIO()
329 {
330 // In case we export (to a file, of course) and the last
331 // filter chose to output a KoDocument we have to save it.
332 // Should be very rare, but well...
333 // Note: m_*input*Document as we already called manageIO()
334 if (m_inputDocument &&
335 static_cast<KoFilterManager::Direction>(filterManagerDirection()) == KoFilterManager::Export) {
336 debugFilter << "Saving the output document to the export file " << m_chainLinks.current()->to();
337 m_inputDocument->setOutputMimeType(m_chainLinks.current()->to());
338 m_inputDocument->saveNativeFormat(filterManagerExportFile());
339 m_inputFile = filterManagerExportFile();
340 }
341 }
342
createTempFile(QTemporaryFile ** tempFile,bool autoDelete)343 bool KoFilterChain::createTempFile(QTemporaryFile** tempFile, bool autoDelete)
344 {
345 if (*tempFile) {
346 errorFilter << "Ooops, why is there already a temp file???" << endl;
347 return false;
348 }
349 *tempFile = new QTemporaryFile();
350 (*tempFile)->setAutoRemove(autoDelete);
351 return (*tempFile)->open();
352 }
353
354 /* Note about Windows & usage of QTemporaryFile
355
356 The QTemporaryFile objects m_inputTempFile and m_outputTempFile are just used
357 to reserve a temporary file with a unique name which then can be used to store
358 an intermediate format. The filters themselves do not get access to these objects,
359 but can query KoFilterChain only for the filename and then have to open the files
360 themselves with their own file handlers (TODO: change this).
361 On Windows this seems to be a problem and results in content not sync'ed to disk etc.
362
363 So unless someone finds out which flags might be needed on opening the files on
364 Windows to prevent this behaviour (unless these details are hidden away by the
365 Qt abstraction and cannot be influenced), a workaround is to destruct the
366 QTemporaryFile objects right after creation again and just take the name,
367 to avoid having two file handlers on the same file.
368
369 A better fix might be to use the QTemporaryFile objects also by the filters,
370 instead of having them open the same file on their own again, but that needs more work
371 and is left for... you :)
372 */
373
inputFileHelper(KoDocument * document,const QString & alternativeFile)374 void KoFilterChain::inputFileHelper(KoDocument* document, const QString& alternativeFile)
375 {
376 if (document) {
377 if (!createTempFile(&m_inputTempFile)) {
378 delete m_inputTempFile;
379 m_inputTempFile = 0;
380 m_inputFile.clear();
381 return;
382 }
383 m_inputFile = m_inputTempFile->fileName();
384 // See "Note about Windows & usage of QTemporaryFile" above
385 #ifdef Q_OS_WIN
386 m_inputTempFile->close();
387 m_inputTempFile->setAutoRemove(true);
388 delete m_inputTempFile;
389 m_inputTempFile = 0;
390 #endif
391 document->setOutputMimeType(m_chainLinks.current()->from());
392 if (!document->saveNativeFormat(m_inputFile)) {
393 delete m_inputTempFile;
394 m_inputTempFile = 0;
395 m_inputFile.clear();
396 return;
397 }
398 } else
399 m_inputFile = alternativeFile;
400 }
401
outputFileHelper(bool autoDelete)402 void KoFilterChain::outputFileHelper(bool autoDelete)
403 {
404 if (!createTempFile(&m_outputTempFile, autoDelete)) {
405 delete m_outputTempFile;
406 m_outputTempFile = 0;
407 m_outputFile.clear();
408 } else {
409 m_outputFile = m_outputTempFile->fileName();
410
411 // See "Note about Windows & usage of QTemporaryFile" above
412 #ifdef Q_OS_WIN
413 m_outputTempFile->close();
414 m_outputTempFile->setAutoRemove(true);
415 delete m_outputTempFile;
416 m_outputTempFile = 0;
417 #endif
418 }
419 }
420
storageNewStreamHelper(KoStore ** storage,KoStoreDevice ** device,const QString & name)421 KoStoreDevice* KoFilterChain::storageNewStreamHelper(KoStore** storage, KoStoreDevice** device,
422 const QString& name)
423 {
424 delete *device;
425 *device = 0;
426 if ((*storage)->isOpen())
427 (*storage)->close();
428 if ((*storage)->bad())
429 return storageCleanupHelper(storage);
430 if (!(*storage)->open(name))
431 return 0;
432
433 *device = new KoStoreDevice(*storage);
434 return *device;
435 }
436
storageHelper(const QString & file,const QString & streamName,KoStore::Mode mode,KoStore ** storage,KoStoreDevice ** device)437 KoStoreDevice* KoFilterChain::storageHelper(const QString& file, const QString& streamName,
438 KoStore::Mode mode, KoStore** storage,
439 KoStoreDevice** device)
440 {
441 if (file.isEmpty())
442 return 0;
443 if (*storage) {
444 debugFilter << "Uh-oh, we forgot to clean up...";
445 return 0;
446 }
447
448 storageInit(file, mode, storage);
449
450 if ((*storage)->bad())
451 return storageCleanupHelper(storage);
452
453 // Seems that we got a valid storage, at least. Even if we can't open
454 // the stream the "user" asked us to open, we nonetheless change the
455 // IOState from File to Storage, as it might be possible to open other streams
456 if (mode == KoStore::Read)
457 m_inputQueried = Storage;
458 else // KoStore::Write
459 m_outputQueried = Storage;
460
461 return storageCreateFirstStream(streamName, storage, device);
462 }
463
storageInit(const QString & file,KoStore::Mode mode,KoStore ** storage)464 void KoFilterChain::storageInit(const QString& file, KoStore::Mode mode, KoStore** storage)
465 {
466 QByteArray appIdentification("");
467 if (mode == KoStore::Write) {
468 // To create valid storages we also have to add the mimetype
469 // magic "applicationIndentifier" to the storage.
470 // As only filters with a Calligra destination should query
471 // for a storage to write to, we don't check the content of
472 // the mimetype here. It doesn't do a lot of harm if someone
473 // "abuses" this method.
474 appIdentification = m_chainLinks.current()->to();
475 }
476 *storage = KoStore::createStore(file, mode, appIdentification);
477 }
478
storageCreateFirstStream(const QString & streamName,KoStore ** storage,KoStoreDevice ** device)479 KoStoreDevice* KoFilterChain::storageCreateFirstStream(const QString& streamName, KoStore** storage,
480 KoStoreDevice** device)
481 {
482 if (!(*storage)->open(streamName))
483 return 0;
484
485 if (*device) {
486 debugFilter << "Uh-oh, we forgot to clean up the storage device!";
487 (*storage)->close();
488 return storageCleanupHelper(storage);
489 }
490 *device = new KoStoreDevice(*storage);
491 return *device;
492 }
493
storageCleanupHelper(KoStore ** storage)494 KoStoreDevice* KoFilterChain::storageCleanupHelper(KoStore** storage)
495 {
496 // Take care not to delete the storage of the parent chain
497 if (*storage != m_outputStorage || !filterManagerParentChain() ||
498 (*storage)->mode() != KoStore::Write)
499 delete *storage;
500 *storage = 0;
501 return 0;
502 }
503
createDocument(const QString & file)504 KoDocument* KoFilterChain::createDocument(const QString& file)
505 {
506 QUrl url;
507 url.setPath(file);
508 QMimeType t = QMimeDatabase().mimeTypeForUrl(url);
509 if (t.isDefault()) {
510 errorFilter << "No mimetype found for " << file << endl;
511 return 0;
512 }
513
514 KoDocument *doc = createDocument(t.name().toLatin1());
515
516 if (!doc || !doc->loadNativeFormat(file)) {
517 errorFilter << "Couldn't load from the file" << endl;
518 delete doc;
519 return 0;
520 }
521 return doc;
522 }
523
createDocument(const QByteArray & mimeType)524 KoDocument* KoFilterChain::createDocument(const QByteArray& mimeType)
525 {
526 KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(mimeType);
527
528 if (entry.isEmpty()) {
529 errorFilter << "Couldn't find a part that can handle mimetype " << mimeType << endl;
530 }
531
532 QString errorMsg;
533 KoPart *part = entry.createKoPart(&errorMsg);
534 if (!part) {
535 errorFilter << "Couldn't create the document: " << errorMsg << endl;
536 return 0;
537 }
538 return part->document();
539 }
540
weight() const541 int KoFilterChain::weight() const
542 {
543 return m_chainLinks.count();
544 }
545