File indexing completed on 2024-04-28 05:08:18
0001 /*************************************************************************** 0002 Copyright (C) 2001-2009 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "document.h" 0026 #include "collectionfactory.h" 0027 #include "translators/tellicoimporter.h" 0028 #include "translators/tellicozipexporter.h" 0029 #include "translators/tellicoxmlexporter.h" 0030 #include "collection.h" 0031 #include "core/filehandler.h" 0032 #include "borrower.h" 0033 #include "fieldformat.h" 0034 #include "core/tellico_strings.h" 0035 #include "images/imagefactory.h" 0036 #include "images/imagedirectory.h" 0037 #include "images/image.h" 0038 #include "images/imageinfo.h" 0039 #include "utils/stringset.h" 0040 #include "utils/mergeconflictresolver.h" 0041 #include "progressmanager.h" 0042 #include "config/tellico_config.h" 0043 #include "entrycomparison.h" 0044 #include "utils/guiproxy.h" 0045 #include "tellico_debug.h" 0046 0047 #include <KMessageBox> 0048 #include <KLocalizedString> 0049 0050 #include <QApplication> 0051 0052 using namespace Tellico; 0053 using Tellico::Data::Document; 0054 Document* Document::s_self = nullptr; 0055 0056 Document::Document() : QObject(), m_coll(nullptr), m_isModified(false), 0057 m_loadAllImages(false), m_validFile(false), m_importer(nullptr), m_cancelImageWriting(true), 0058 m_fileFormat(Import::TellicoImporter::Unknown), m_loadImagesTimer(this) { 0059 m_allImagesOnDisk = Config::imageLocation() != Config::ImagesInFile; 0060 m_loadImagesTimer.setSingleShot(true); 0061 m_loadImagesTimer.setInterval(500); 0062 connect(&m_loadImagesTimer, &QTimer::timeout, this, &Document::slotLoadAllImages); 0063 newDocument(Collection::Book); 0064 } 0065 0066 Document::~Document() { 0067 delete m_importer; 0068 m_importer = nullptr; 0069 } 0070 0071 Tellico::Data::CollPtr Document::collection() const { 0072 return m_coll; 0073 } 0074 0075 void Document::setURL(const QUrl& url_) { 0076 m_url = url_; 0077 if(m_url.fileName() != i18n(Tellico::untitledFilename)) { 0078 ImageFactory::setLocalDirectory(m_url); 0079 EntryComparison::setDocumentUrl(m_url); 0080 } 0081 } 0082 0083 void Document::setModified(bool modified_) { 0084 if(modified_ != m_isModified) { 0085 m_isModified = modified_; 0086 emit signalModified(m_isModified); 0087 } 0088 } 0089 0090 void Document::slotSetModified() { 0091 setModified(true); 0092 } 0093 0094 /** 0095 * Since QUndoStack emits cleanChanged(), the behavior is opposite 0096 * the document modified flag 0097 */ 0098 void Document::slotSetClean(bool clean_) { 0099 setModified(!clean_); 0100 } 0101 0102 bool Document::newDocument(int type_) { 0103 if(m_importer) { 0104 m_importer->deleteLater(); 0105 m_importer = nullptr; 0106 } 0107 deleteContents(); 0108 0109 m_coll = CollectionFactory::collection(type_, true); 0110 m_coll->setTrackGroups(true); 0111 0112 // be sure to set the new Url before signalling a new collection 0113 // since reading options for custom collections depend on the file name 0114 const QUrl url = QUrl::fromLocalFile(i18n(Tellico::untitledFilename)); 0115 setURL(url); 0116 0117 emit signalCollectionAdded(m_coll); 0118 emit signalCollectionImagesLoaded(m_coll); 0119 0120 setModified(false); 0121 m_validFile = false; 0122 m_fileFormat = Import::TellicoImporter::Unknown; 0123 0124 return true; 0125 } 0126 0127 bool Document::openDocument(const QUrl& url_) { 0128 MARK; 0129 if(url_.isEmpty()) { 0130 return false; 0131 } 0132 // delayed image loading only works for local files 0133 m_loadAllImages = !url_.isLocalFile(); 0134 m_loadImagesTimer.stop(); // avoid potential race condition 0135 0136 if(m_importer) { 0137 m_importer->deleteLater(); 0138 } 0139 m_importer = new Import::TellicoImporter(url_, m_loadAllImages); 0140 0141 ProgressItem& item = ProgressManager::self()->newProgressItem(m_importer, m_importer->progressLabel(), true); 0142 connect(m_importer, &Import::Importer::signalTotalSteps, 0143 ProgressManager::self(), &ProgressManager::setTotalSteps); 0144 connect(m_importer, &Import::Importer::signalProgress, 0145 ProgressManager::self(), &ProgressManager::setProgress); 0146 connect(&item, &ProgressItem::signalCancelled, m_importer, &Import::Importer::slotCancel); 0147 ProgressItem::Done done(m_importer); 0148 0149 CollPtr coll = m_importer->collection(); 0150 if(!m_importer) { 0151 myDebug() << "The importer was deleted out from under us"; 0152 return false; 0153 } 0154 // delayed image loading only works for zip files 0155 // format is only known AFTER collection() is called 0156 0157 m_fileFormat = m_importer->format(); 0158 m_allImagesOnDisk = !m_importer->hasImages(); 0159 if(!m_importer->hasImages() || m_fileFormat != Import::TellicoImporter::Zip) { 0160 m_loadAllImages = true; 0161 } 0162 ImageFactory::setZipArchive(m_importer->takeImages()); 0163 0164 if(!coll) { 0165 myLog() << "Failed to read a collection" << m_importer->statusMessage(); 0166 GUI::Proxy::sorry(m_importer->statusMessage()); 0167 m_validFile = false; 0168 return false; 0169 } 0170 deleteContents(); 0171 m_coll = coll; 0172 m_coll->setTrackGroups(true); 0173 setURL(url_); 0174 m_validFile = true; 0175 0176 emit signalCollectionAdded(m_coll); 0177 0178 // m_importer might have been deleted? 0179 setModified(m_importer && m_importer->modifiedOriginal()); 0180 // if(pruneImages()) { 0181 // slotSetModified(true); 0182 // } 0183 if(m_importer && m_importer->hasImages()) { 0184 m_cancelImageWriting = false; 0185 m_loadImagesTimer.start(); 0186 } else { 0187 emit signalCollectionImagesLoaded(m_coll); 0188 if(m_importer) { 0189 m_importer->deleteLater(); 0190 m_importer = nullptr; 0191 } 0192 } 0193 return true; 0194 } 0195 0196 bool Document::saveDocument(const QUrl& url_, bool force_) { 0197 // FileHandler::queryExists calls FileHandler::writeBackupFile 0198 // so the only reason to check queryExists() is if the url to write to is different than the current one 0199 if(url_ == m_url) { 0200 if(!FileHandler::writeBackupFile(url_)) { 0201 return false; 0202 } 0203 } else { 0204 if(!force_ && !FileHandler::queryExists(url_)) { 0205 return false; 0206 } 0207 } 0208 0209 // in case we're still loading images, give that a chance to cancel 0210 m_cancelImageWriting = true; 0211 qApp->processEvents(); 0212 0213 ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Saving file..."), false); 0214 ProgressItem::Done done(this); 0215 0216 // will always save as zip file, no matter if has images or not 0217 int imageLocation = Config::imageLocation(); 0218 bool includeImages = imageLocation == Config::ImagesInFile; 0219 int totalSteps; 0220 // write all images to disk cache if needed 0221 // have to do this before executing exporter in case 0222 // the user changed the imageInFile setting from Yes to No, in which 0223 // case saving will overwrite the old file that has the images in it! 0224 if(includeImages) { 0225 totalSteps = 10; 0226 item.setTotalSteps(totalSteps); 0227 // since TellicoZipExporter uses 100 steps, then it will get 100/110 of the total progress 0228 } else { 0229 totalSteps = 100; 0230 item.setTotalSteps(totalSteps); 0231 m_cancelImageWriting = false; 0232 writeAllImages(imageLocation == Config::ImagesInAppDir ? ImageFactory::DataDir : ImageFactory::LocalDir, url_); 0233 } 0234 QScopedPointer<Export::Exporter> exporter; 0235 if(m_fileFormat == Import::TellicoImporter::XML) { 0236 exporter.reset(new Export::TellicoXMLExporter(m_coll)); 0237 static_cast<Export::TellicoXMLExporter*>(exporter.data())->setIncludeImages(includeImages); 0238 } else { 0239 exporter.reset(new Export::TellicoZipExporter(m_coll)); 0240 static_cast<Export::TellicoZipExporter*>(exporter.data())->setIncludeImages(includeImages); 0241 } 0242 item.setProgress(int(0.8*totalSteps)); 0243 exporter->setEntries(m_coll->entries()); 0244 exporter->setURL(url_); 0245 // since we already asked about overwriting the file, force the save 0246 long opt = exporter->options() | Export::ExportForce | Export::ExportComplete | Export::ExportProgress; 0247 // only write the image sizes if they're known already 0248 opt &= ~Export::ExportImageSize; 0249 exporter->setOptions(opt); 0250 const bool success = exporter->exec(); 0251 item.setProgress(int(0.9*totalSteps)); 0252 0253 if(success) { 0254 setURL(url_); 0255 // if successful, doc is no longer modified 0256 setModified(false); 0257 } else { 0258 myLog() << "Document::saveDocument() - not successful saving to" << url_.toDisplayString(QUrl::PreferLocalFile); 0259 } 0260 return success; 0261 } 0262 0263 bool Document::saveDocumentTemplate(const QUrl& url_, const QString& title_) { 0264 Data::CollPtr collTemplate = CollectionFactory::collection(m_coll->type(), false /* no default fields */); 0265 collTemplate->setTitle(title_); 0266 // add the fields from the current collection 0267 foreach(auto field, m_coll->fields()) { 0268 collTemplate->addField(field); 0269 } 0270 foreach(auto filter, m_coll->filters()) { 0271 collTemplate->addFilter(filter); 0272 } 0273 QScopedPointer<Export::Exporter> exporter(new Export::TellicoXMLExporter(collTemplate)); 0274 exporter->setURL(url_); 0275 // since we already asked about overwriting the file, force the save 0276 exporter->setOptions(exporter->options() | Export::ExportForce | Export::ExportComplete); 0277 return exporter->exec(); 0278 } 0279 0280 bool Document::closeDocument() { 0281 if(m_importer) { 0282 m_importer->deleteLater(); 0283 m_importer = nullptr; 0284 } 0285 deleteContents(); 0286 return true; 0287 } 0288 0289 void Document::deleteContents() { 0290 if(m_coll) { 0291 emit signalCollectionDeleted(m_coll); 0292 } 0293 // don't delete the m_importer here, bad things will happen 0294 0295 // since the collection holds a pointer to each entry and each entry 0296 // hold a pointer to the collection, and they're both sharedptrs, 0297 // neither will ever get deleted, unless the entries are removed from the collection 0298 if(m_coll) { 0299 m_coll->clear(); 0300 } 0301 m_coll = nullptr; // old collection gets deleted as refcount goes to 0 0302 m_cancelImageWriting = true; 0303 } 0304 0305 void Document::appendCollection(Tellico::Data::CollPtr coll_) { 0306 bool structuralChange = false; 0307 appendCollection(m_coll, coll_, &structuralChange); 0308 emit signalCollectionModified(m_coll, structuralChange); 0309 } 0310 0311 void Document::appendCollection(Tellico::Data::CollPtr coll1_, Tellico::Data::CollPtr coll2_, bool* structuralChange_) { 0312 if(structuralChange_) *structuralChange_ = false; 0313 if(!coll1_ || !coll2_) { 0314 return; 0315 } 0316 0317 coll1_->blockSignals(true); 0318 0319 foreach(FieldPtr field, coll2_->fields()) { 0320 bool collChange = coll1_->mergeField(field); 0321 if(collChange && structuralChange_) *structuralChange_ = true; 0322 } 0323 0324 Data::EntryList newEntries; 0325 foreach(EntryPtr entry, coll2_->entries()) { 0326 Data::EntryPtr newEntry(new Data::Entry(*entry)); 0327 newEntry->setCollection(coll1_); 0328 newEntries << newEntry; 0329 } 0330 coll1_->addEntries(newEntries); 0331 // TODO: merge filters and loans 0332 coll1_->blockSignals(false); 0333 } 0334 0335 Tellico::Data::MergePair Document::mergeCollection(Tellico::Data::CollPtr coll_) { 0336 bool structuralChange = false; 0337 const auto mergeResult = mergeCollection(m_coll, coll_, &structuralChange); 0338 emit signalCollectionModified(m_coll, structuralChange); 0339 return mergeResult; 0340 } 0341 0342 Tellico::Data::MergePair Document::mergeCollection(Tellico::Data::CollPtr coll1_, Tellico::Data::CollPtr coll2_, bool* structuralChange_) { 0343 if(structuralChange_) *structuralChange_ = false; 0344 MergePair pair; 0345 if(!coll1_ || !coll2_) { 0346 return pair; 0347 } 0348 0349 coll1_->blockSignals(true); 0350 Data::FieldList fields = coll2_->fields(); 0351 foreach(FieldPtr field, fields) { 0352 bool collChange = coll1_->mergeField(field); 0353 if(collChange && structuralChange_) *structuralChange_ = true; 0354 } 0355 0356 EntryList currEntries = coll1_->entries(); 0357 EntryList newEntries = coll2_->entries(); 0358 std::sort(currEntries.begin(), currEntries.end(), Data::EntryCmp(QStringLiteral("title"))); 0359 std::sort(newEntries.begin(), newEntries.end(), Data::EntryCmp(QStringLiteral("title"))); 0360 0361 const int currTotal = currEntries.count(); 0362 int lastMatchId = 0; 0363 bool checkSameId = false; // if the matching entries have the same id, then check that first for later comparisons 0364 foreach(EntryPtr newEntry, newEntries) { 0365 int bestMatch = 0; 0366 Data::EntryPtr matchEntry, currEntry; 0367 // first, if we're checking against same ID 0368 if(checkSameId) { 0369 currEntry = coll1_->entryById(newEntry->id()); 0370 if(currEntry && coll1_->sameEntry(currEntry, newEntry) >= EntryComparison::ENTRY_PERFECT_MATCH) { 0371 // only have to compare against perfect match 0372 matchEntry = currEntry; 0373 } 0374 } 0375 if(!matchEntry) { 0376 // alternative is to loop over them all 0377 for(int i = 0; i < currTotal; ++i) { 0378 // since we're sorted by title, track the index of the previous match and start comparison there 0379 currEntry = currEntries.at((i+lastMatchId) % currTotal); 0380 const int match = coll1_->sameEntry(currEntry, newEntry); 0381 if(match >= EntryComparison::ENTRY_PERFECT_MATCH) { 0382 matchEntry = currEntry; 0383 lastMatchId = (i+lastMatchId) % currTotal; 0384 break; 0385 } else if(match >= EntryComparison::ENTRY_GOOD_MATCH && match > bestMatch) { 0386 bestMatch = match; 0387 matchEntry = currEntry; 0388 lastMatchId = (i+lastMatchId) % currTotal; 0389 // don't break, keep looking for better one 0390 } 0391 } 0392 } 0393 if(matchEntry) { 0394 checkSameId = checkSameId || (matchEntry->id() == newEntry->id()); 0395 Merge::mergeEntry(matchEntry, newEntry); 0396 } else { 0397 Data::EntryPtr e(new Data::Entry(*newEntry)); 0398 e->setCollection(coll1_); 0399 // keep track of which entries got added 0400 pair.first.append(e); 0401 } 0402 } 0403 coll1_->addEntries(pair.first); 0404 // TODO: merge filters and loans 0405 coll1_->blockSignals(false); 0406 return pair; 0407 } 0408 0409 void Document::replaceCollection(Tellico::Data::CollPtr coll_) { 0410 if(!coll_) { 0411 return; 0412 } 0413 0414 QUrl url = QUrl::fromLocalFile(i18n(Tellico::untitledFilename)); 0415 setURL(url); 0416 m_validFile = false; 0417 0418 emit signalCollectionDeleted(m_coll); 0419 m_coll = coll_; 0420 m_coll->setTrackGroups(true); 0421 m_cancelImageWriting = true; 0422 emit signalCollectionAdded(m_coll); 0423 } 0424 0425 void Document::unAppendCollection(Tellico::Data::FieldList origFields_, QList<int> addedEntries_) { 0426 m_coll->blockSignals(true); 0427 bool structuralChange = false; 0428 0429 StringSet origFieldNames; 0430 foreach(FieldPtr field, origFields_) { 0431 m_coll->modifyField(field); 0432 origFieldNames.add(field->name()); 0433 structuralChange = true; 0434 } 0435 0436 EntryList entriesToRemove; 0437 foreach(int id, addedEntries_) { 0438 auto e = m_coll->entryById(id); 0439 if(e) entriesToRemove << e; 0440 } 0441 m_coll->removeEntries(entriesToRemove); 0442 0443 // since Collection::removeField() iterates over all entries to reset the value of the field 0444 // don't removeField() until after removeEntry() is done 0445 FieldList currFields = m_coll->fields(); 0446 foreach(FieldPtr field, currFields) { 0447 if(!origFieldNames.has(field->name())) { 0448 m_coll->removeField(field); 0449 structuralChange = true; 0450 } 0451 } 0452 m_coll->blockSignals(false); 0453 emit signalCollectionModified(m_coll, structuralChange); 0454 } 0455 0456 void Document::unMergeCollection(Tellico::Data::FieldList origFields_, Tellico::Data::MergePair entryPair_) { 0457 m_coll->blockSignals(true); 0458 bool structuralChange = false; 0459 0460 QStringList origFieldNames; 0461 foreach(FieldPtr field, origFields_) { 0462 m_coll->modifyField(field); 0463 origFieldNames << field->name(); 0464 structuralChange = true; 0465 } 0466 0467 // first item in pair are the entries added by the operation, remove them 0468 EntryList entries = entryPair_.first; 0469 m_coll->removeEntries(entries); 0470 0471 // second item in pair are the entries which got modified by the original merge command 0472 const QString track = QStringLiteral("track"); 0473 PairVector trackChanges = entryPair_.second; 0474 // need to go through them in reverse since one entry may have been modified multiple times 0475 // first item in the pair is the entry pointer 0476 // second item is the old value of the track field 0477 for(int i = trackChanges.count()-1; i >= 0; --i) { 0478 trackChanges[i].first->setField(track, trackChanges[i].second); 0479 } 0480 0481 // since Collection::removeField() iterates over all entries to reset the value of the field 0482 // don't removeField() until after removeEntry() is done 0483 FieldList currFields = m_coll->fields(); 0484 foreach(FieldPtr field, currFields) { 0485 if(origFieldNames.indexOf(field->name()) == -1) { 0486 m_coll->removeField(field); 0487 structuralChange = true; 0488 } 0489 } 0490 m_coll->blockSignals(false); 0491 emit signalCollectionModified(m_coll, structuralChange); 0492 } 0493 0494 bool Document::isEmpty() const { 0495 //an empty doc may contain a collection, but no entries 0496 return (!m_coll || m_coll->entries().isEmpty()); 0497 } 0498 0499 bool Document::loadAllImagesNow() const { 0500 // DEBUG_LINE; 0501 if(!m_coll || !m_validFile) { 0502 return false; 0503 } 0504 if(m_loadAllImages) { 0505 myDebug() << "Document::loadAllImagesNow() - all valid images should already be loaded!"; 0506 return false; 0507 } 0508 return Import::TellicoImporter::loadAllImages(m_url); 0509 } 0510 0511 Tellico::Data::EntryList Document::filteredEntries(Tellico::FilterPtr filter_) const { 0512 Data::EntryList matches; 0513 Data::EntryList entries = m_coll->entries(); 0514 foreach(EntryPtr entry, entries) { 0515 if(filter_->matches(entry)) { 0516 matches.append(entry); 0517 } 0518 } 0519 return matches; 0520 } 0521 0522 void Document::checkOutEntry(Tellico::Data::EntryPtr entry_) { 0523 if(!entry_) { 0524 return; 0525 } 0526 0527 const QString loaned = QStringLiteral("loaned"); 0528 if(!m_coll->hasField(loaned)) { 0529 FieldPtr f(new Field(loaned, i18n("Loaned"), Field::Bool)); 0530 f->setFlags(Field::AllowGrouped); 0531 f->setCategory(i18n("Personal")); 0532 m_coll->addField(f); 0533 } 0534 entry_->setField(loaned, QStringLiteral("true")); 0535 EntryList vec; 0536 vec.append(entry_); 0537 m_coll->updateDicts(vec, QStringList() << loaned); 0538 } 0539 0540 void Document::checkInEntry(Tellico::Data::EntryPtr entry_) { 0541 if(!entry_) { 0542 return; 0543 } 0544 0545 const QString loaned = QStringLiteral("loaned"); 0546 if(!m_coll->hasField(loaned)) { 0547 return; 0548 } 0549 entry_->setField(loaned, QString()); 0550 m_coll->updateDicts(EntryList() << entry_, QStringList() << loaned); 0551 } 0552 0553 void Document::renameCollection(const QString& newTitle_) { 0554 m_coll->setTitle(newTitle_); 0555 } 0556 0557 // this only gets called when a zip file with images is opened 0558 // by loading every image, it gets pulled out of the zip file and 0559 // copied to disk. Then the zip file can be closed and not retained in memory 0560 void Document::slotLoadAllImages() { 0561 QString id; 0562 StringSet images; 0563 foreach(EntryPtr entry, m_coll->entries()) { 0564 foreach(FieldPtr field, m_coll->imageFields()) { 0565 id = entry->field(field); 0566 if(id.isEmpty() || images.has(id)) { 0567 continue; 0568 } 0569 // this is the early loading, so just by calling imageById() 0570 // the image gets sucked from the zip file and written to disk 0571 // by ImageFactory::imageById() 0572 // TODO:: does this need to check against images with link only? 0573 if(ImageFactory::imageById(id).isNull()) { 0574 myLog() << "Null image for entry:" << entry->title() << id; 0575 } 0576 images.add(id); 0577 if(m_cancelImageWriting) { 0578 break; 0579 } 0580 } 0581 if(m_cancelImageWriting) { 0582 break; 0583 } 0584 // stay responsive, do this in the background 0585 qApp->processEvents(); 0586 } 0587 0588 if(m_cancelImageWriting) { 0589 myLog() << "slotLoadAllImages() - cancel image writing"; 0590 } else { 0591 emit signalCollectionImagesLoaded(m_coll); 0592 } 0593 0594 m_cancelImageWriting = false; 0595 if(m_importer) { 0596 m_importer->deleteLater(); 0597 m_importer = nullptr; 0598 } 0599 } 0600 0601 // cacheDir_ is the location dir to write the images 0602 // localDir_ provide the new file location which is only needed if cacheDir == LocalDir 0603 void Document::writeAllImages(int cacheDir_, const QUrl& localDir_) { 0604 // images get 80 steps in saveDocument() 0605 const uint stepSize = 1 + qMax(1, m_coll->entryCount()/80); // add 1 since it could round off 0606 uint j = 1; 0607 0608 ImageFactory::CacheDir cacheDir = static_cast<ImageFactory::CacheDir>(cacheDir_); 0609 QScopedPointer<ImageDirectory> imgDir; 0610 if(cacheDir == ImageFactory::LocalDir) { 0611 imgDir.reset(new ImageDirectory(ImageFactory::localDirectory(localDir_))); 0612 } 0613 0614 QString id; 0615 StringSet images; 0616 EntryList entries = m_coll->entries(); 0617 FieldList imageFields = m_coll->imageFields(); 0618 foreach(EntryPtr entry, entries) { 0619 foreach(FieldPtr field, imageFields) { 0620 id = entry->field(field); 0621 if(id.isEmpty() || images.has(id)) { 0622 continue; 0623 } 0624 images.add(id); 0625 if(ImageFactory::imageInfo(id).linkOnly) { 0626 continue; 0627 } 0628 // careful here, if we're writing to LocalDir, need to read from the old LocalDir and write to new 0629 bool success; 0630 if(cacheDir == ImageFactory::LocalDir) { 0631 success = ImageFactory::writeCachedImage(id, imgDir.data()); 0632 } else { 0633 success = ImageFactory::writeCachedImage(id, cacheDir); 0634 } 0635 if(!success) { 0636 myLog() << "Failed to write image for entry title:" << entry->title(); 0637 } 0638 if(m_cancelImageWriting) { 0639 break; 0640 } 0641 } 0642 if(j%stepSize == 0) { 0643 ProgressManager::self()->setProgress(this, j/stepSize); 0644 } 0645 ++j; 0646 if(m_cancelImageWriting) { 0647 break; 0648 } 0649 } 0650 0651 if(m_cancelImageWriting) { 0652 myLog() << "Document::writeAllImages() - image writing was cancelled"; 0653 } 0654 0655 m_cancelImageWriting = false; 0656 } 0657 0658 bool Document::pruneImages() { 0659 bool found = false; 0660 QString id; 0661 StringSet images; 0662 Data::EntryList entries = m_coll->entries(); 0663 Data::FieldList imageFields = m_coll->imageFields(); 0664 foreach(EntryPtr entry, entries) { 0665 foreach(FieldPtr field, imageFields) { 0666 id = entry->field(field); 0667 if(id.isEmpty() || images.has(id)) { 0668 continue; 0669 } 0670 const Data::Image& img = ImageFactory::imageById(id); 0671 if(img.isNull()) { 0672 entry->setField(field, QString()); 0673 found = true; 0674 myDebug() << "removing null image for" << entry->title() << ":" << id; 0675 } else { 0676 images.add(id); 0677 } 0678 } 0679 } 0680 return found; 0681 } 0682 0683 int Document::imageCount() const { 0684 if(!m_coll) { 0685 return 0; 0686 } 0687 StringSet images; 0688 FieldList fields = m_coll->imageFields(); 0689 EntryList entries = m_coll->entries(); 0690 foreach(FieldPtr field, fields) { 0691 foreach(EntryPtr entry, entries) { 0692 images.add(entry->field(field)); 0693 } 0694 } 0695 return images.count(); 0696 } 0697 0698 void Document::removeImagesNotInCollection(Tellico::Data::EntryList entries_, Tellico::Data::EntryList entriesToKeep_) { 0699 // first get list of all images in collection 0700 StringSet images; 0701 FieldList fields = m_coll->imageFields(); 0702 EntryList allEntries = m_coll->entries(); 0703 foreach(FieldPtr field, fields) { 0704 foreach(EntryPtr entry, allEntries) { 0705 images.add(entry->field(field)); 0706 } 0707 foreach(EntryPtr entry, entriesToKeep_) { 0708 images.add(entry->field(field)); 0709 } 0710 } 0711 0712 // now for all images not in the cache, we can clear them 0713 StringSet imagesToCheck = ImageFactory::imagesNotInCache(); 0714 0715 // if entries_ is not empty, that means we want to limit the images removed 0716 // to those that are referenced in those entries 0717 StringSet imagesToRemove; 0718 foreach(FieldPtr field, fields) { 0719 foreach(EntryPtr entry, entries_) { 0720 QString id = entry->field(field); 0721 if(!id.isEmpty() && imagesToCheck.has(id) && !images.has(id)) { 0722 imagesToRemove.add(id); 0723 } 0724 } 0725 } 0726 0727 const QStringList realImagesToRemove = imagesToRemove.values(); 0728 for(QStringList::ConstIterator it = realImagesToRemove.begin(); it != realImagesToRemove.end(); ++it) { 0729 ImageFactory::removeImage(*it, false); // doesn't delete, just remove link 0730 } 0731 }