File indexing completed on 2025-01-05 03:55:17
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-06-02 0007 * Description : class for manipulating modifications changeset for non-destruct. editing 0008 * 0009 * SPDX-FileCopyrightText: 2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2010 by Martin Klapetek <martin dot klapetek at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "dimagehistory.h" 0017 0018 // Qt includes 0019 0020 #include <QFile> 0021 #include <QSharedData> 0022 #include <QBuffer> 0023 #include <QHashIterator> 0024 #include <QFileInfo> 0025 #include <QUrl> 0026 0027 // Local includes 0028 0029 #include "digikam_debug.h" 0030 0031 namespace Digikam 0032 { 0033 0034 class Q_DECL_HIDDEN DImageHistory::Private : public QSharedData 0035 { 0036 public: 0037 0038 explicit Private() 0039 { 0040 } 0041 0042 QList<DImageHistory::Entry> entries; 0043 }; 0044 0045 // ----------------------------------------------------------------------------------------------- 0046 0047 class Q_DECL_HIDDEN PrivateSharedNull : public QSharedDataPointer<DImageHistory::Private> 0048 { 0049 public: 0050 0051 PrivateSharedNull() 0052 : QSharedDataPointer<DImageHistory::Private>(new DImageHistory::Private) 0053 { 0054 } 0055 }; 0056 0057 Q_GLOBAL_STATIC(PrivateSharedNull, imageHistoryPrivSharedNull) 0058 0059 // ----------------------------------------------------------------------------------------------- 0060 0061 DImageHistory::DImageHistory() 0062 : d(*imageHistoryPrivSharedNull) 0063 { 0064 } 0065 0066 DImageHistory::DImageHistory(const DImageHistory& other) 0067 : d(other.d) 0068 { 0069 } 0070 0071 DImageHistory::~DImageHistory() 0072 { 0073 } 0074 0075 DImageHistory& DImageHistory::operator=(const DImageHistory& other) 0076 { 0077 d = other.d; 0078 0079 return *this; 0080 } 0081 0082 bool DImageHistory::isNull() const 0083 { 0084 return (d == *imageHistoryPrivSharedNull); 0085 } 0086 0087 bool DImageHistory::isEmpty() const 0088 { 0089 return d->entries.isEmpty(); 0090 } 0091 0092 bool DImageHistory::isValid() const 0093 { 0094 if (d->entries.isEmpty()) 0095 { 0096 return false; 0097 } 0098 else if ((d->entries.count() == 1) && 0099 (d->entries.first().referredImages.count() == 1) && 0100 d->entries.first().referredImages.first().isCurrentFile()) 0101 { 0102 return false; 0103 } 0104 else 0105 { 0106 Q_FOREACH (const Entry& e, d->entries) 0107 { 0108 if (!e.action.isNull()) 0109 { 0110 return true; 0111 } 0112 0113 Q_FOREACH (const HistoryImageId& id, e.referredImages) 0114 { 0115 if (id.isValid() && !id.isCurrentFile()) 0116 { // cppcheck-suppress useStlAlgorithm 0117 return true; 0118 } 0119 } 0120 } 0121 } 0122 return false; 0123 } 0124 0125 int DImageHistory::size() const 0126 { 0127 return d->entries.size(); 0128 } 0129 0130 static bool operator==(const DImageHistory::Entry& e1, const DImageHistory::Entry& e2) 0131 { 0132 return ((e1.action == e2.action) && 0133 (e1.referredImages == e2.referredImages)); 0134 } 0135 0136 bool DImageHistory::operator==(const DImageHistory& other) const 0137 { 0138 return (d->entries == other.d->entries); 0139 } 0140 0141 bool DImageHistory::operator<(const Digikam::DImageHistory& other) const 0142 { 0143 if (d->entries.size() < other.size()) 0144 { 0145 return true; 0146 } 0147 0148 return false; 0149 } 0150 0151 bool DImageHistory::operator>(const Digikam::DImageHistory& other) const 0152 { 0153 if (d->entries.size() > other.size()) 0154 { 0155 return true; 0156 } 0157 0158 return false; 0159 } 0160 0161 QList<DImageHistory::Entry> &DImageHistory::entries() 0162 { 0163 return d->entries; 0164 } 0165 0166 const QList<DImageHistory::Entry> &DImageHistory::entries() const 0167 { 0168 return d->entries; 0169 } 0170 0171 DImageHistory::Entry& DImageHistory::operator[](int i) 0172 { 0173 return d->entries[i]; 0174 } 0175 0176 const DImageHistory::Entry& DImageHistory::operator[](int i) const 0177 { 0178 return d->entries.at(i); 0179 } 0180 0181 DImageHistory& DImageHistory::operator<<(const FilterAction& action) 0182 { 0183 if (action.isNull()) 0184 { 0185 return *this; 0186 } 0187 0188 Entry entry; 0189 entry.action = action; 0190 d->entries << entry; 0191 /* 0192 qCDebug(DIGIKAM_DIMG_LOG) << "Entry added, total count " << d->entries.count(); 0193 */ 0194 return *this; 0195 } 0196 0197 DImageHistory& DImageHistory::operator<<(const HistoryImageId& id) 0198 { 0199 appendReferredImage(id); 0200 0201 return *this; 0202 } 0203 0204 void DImageHistory::appendReferredImage(const HistoryImageId& id) 0205 { 0206 insertReferredImage(d->entries.size() - 1, id); 0207 } 0208 0209 void DImageHistory::insertReferredImage(int index, const HistoryImageId& id) 0210 { 0211 if (!id.isValid()) 0212 { 0213 qCWarning(DIGIKAM_DIMG_LOG) << "Attempt to add an invalid HistoryImageId"; 0214 return; 0215 } 0216 0217 // NOTE: Qt6::qBound() generate an exception if d->entries is empty, Qt5 no. 0218 0219 index = qMax(0, qMin(d->entries.size() - 1, index)); 0220 0221 if (id.isCurrentFile()) 0222 { 0223 // enforce to have exactly one Current id 0224 0225 adjustReferredImages(); 0226 } 0227 0228 if (d->entries.isEmpty()) 0229 { 0230 d->entries << Entry(); 0231 } 0232 0233 d->entries[index].referredImages << id; 0234 } 0235 0236 void DImageHistory::removeLast() 0237 { 0238 if (!d->entries.isEmpty()) 0239 { 0240 d->entries.removeLast(); 0241 } 0242 } 0243 0244 const FilterAction& DImageHistory::action(int i) const 0245 { 0246 return d->entries.at(i).action; 0247 } 0248 0249 QList<FilterAction> DImageHistory::allActions() const 0250 { 0251 QList<FilterAction> actions; 0252 0253 Q_FOREACH (const Entry& entry, d->entries) 0254 { 0255 if (!entry.action.isNull()) 0256 { 0257 actions << entry.action; 0258 } 0259 } 0260 0261 return actions; 0262 } 0263 0264 int DImageHistory::actionCount() const 0265 { 0266 int count = 0; 0267 0268 Q_FOREACH (const Entry& entry, d->entries) 0269 { 0270 if (!entry.action.isNull()) 0271 { 0272 ++count; // cppcheck-suppress useStlAlgorithm 0273 } 0274 } 0275 0276 return count; 0277 } 0278 0279 bool DImageHistory::hasActions() const 0280 { 0281 Q_FOREACH (const Entry& entry, d->entries) 0282 { 0283 if (!entry.action.isNull()) 0284 { // cppcheck-suppress useStlAlgorithm 0285 return true; 0286 } 0287 } 0288 0289 return false; 0290 } 0291 0292 QList<HistoryImageId> &DImageHistory::referredImages(int i) 0293 { 0294 return d->entries[i].referredImages; 0295 } 0296 0297 const QList<HistoryImageId> &DImageHistory::referredImages(int i) const 0298 { 0299 return d->entries.at(i).referredImages; 0300 } 0301 0302 QList<HistoryImageId> DImageHistory::allReferredImages() const 0303 { 0304 QList<HistoryImageId> ids; 0305 0306 Q_FOREACH (const Entry& entry, d->entries) 0307 { 0308 ids << entry.referredImages; 0309 } 0310 0311 return ids; 0312 } 0313 0314 bool DImageHistory::hasReferredImages() const 0315 { 0316 Q_FOREACH (const Entry& entry, d->entries) 0317 { 0318 if (!entry.referredImages.isEmpty()) 0319 { // cppcheck-suppress useStlAlgorithm 0320 return true; 0321 } 0322 } 0323 0324 return false; 0325 } 0326 0327 bool DImageHistory::hasReferredImageOfType(HistoryImageId::Type type) const 0328 { 0329 Q_FOREACH (const Entry& entry, d->entries) 0330 { 0331 Q_FOREACH (const HistoryImageId& id, entry.referredImages) 0332 { 0333 if (id.m_type == type) 0334 { // cppcheck-suppress useStlAlgorithm 0335 return true; 0336 } 0337 } 0338 } 0339 0340 return false; 0341 } 0342 0343 bool DImageHistory::hasCurrentReferredImage() const 0344 { 0345 return hasReferredImageOfType(HistoryImageId::Current); 0346 } 0347 0348 bool DImageHistory::hasOriginalReferredImage() const 0349 { 0350 return hasReferredImageOfType(HistoryImageId::Original); 0351 } 0352 0353 QList<HistoryImageId> DImageHistory::referredImagesOfType(HistoryImageId::Type type) const 0354 { 0355 QList<HistoryImageId> ids; 0356 0357 Q_FOREACH (const Entry& entry, d->entries) 0358 { 0359 Q_FOREACH (const HistoryImageId& id, entry.referredImages) 0360 { 0361 if (id.m_type == type) 0362 { 0363 ids << id; 0364 } 0365 } 0366 } 0367 0368 return ids; 0369 } 0370 0371 HistoryImageId DImageHistory::currentReferredImage() const 0372 { 0373 Q_FOREACH (const Entry& entry, d->entries) 0374 { 0375 Q_FOREACH (const HistoryImageId& id, entry.referredImages) 0376 { 0377 if (id.isCurrentFile()) 0378 { // cppcheck-suppress useStlAlgorithm 0379 return id; 0380 } 0381 } 0382 } 0383 0384 return HistoryImageId(); 0385 } 0386 0387 HistoryImageId DImageHistory::originalReferredImage() const 0388 { 0389 Q_FOREACH (const Entry& entry, d->entries) 0390 { 0391 Q_FOREACH (const HistoryImageId& id, entry.referredImages) 0392 { 0393 if (id.isOriginalFile()) 0394 { // cppcheck-suppress useStlAlgorithm 0395 return id; 0396 } 0397 } 0398 } 0399 0400 return HistoryImageId(); 0401 } 0402 0403 void DImageHistory::clearReferredImages() 0404 { 0405 for (int i = 0 ; i < d->entries.size() ; ++i) 0406 { 0407 d->entries[i].referredImages.clear(); 0408 } 0409 } 0410 0411 void DImageHistory::adjustReferredImages() 0412 { 0413 for (int i = 0 ; i < d->entries.size() ; ++i) 0414 { 0415 Entry& entry = d->entries[i]; 0416 0417 for (int e = 0 ; e < entry.referredImages.size() ; ++e) 0418 { 0419 HistoryImageId& id = entry.referredImages[e]; 0420 0421 if (id.isCurrentFile()) 0422 { 0423 id.m_type = (i == 0) ? HistoryImageId::Original 0424 : HistoryImageId::Intermediate; 0425 } 0426 } 0427 } 0428 } 0429 0430 void DImageHistory::adjustCurrentUuid(const QString& uuid) 0431 { 0432 for (int i = 0 ; i < d->entries.size() ; ++i) 0433 { 0434 Entry& entry = d->entries[i]; 0435 0436 for (int e = 0 ; e < entry.referredImages.size() ; ++e) 0437 { 0438 HistoryImageId& id = entry.referredImages[e]; 0439 0440 if (id.isCurrentFile()) 0441 { 0442 if (id.m_uuid.isNull()) 0443 { 0444 id.m_uuid = uuid; 0445 } 0446 } 0447 } 0448 } 0449 } 0450 0451 void DImageHistory::purgePathFromReferredImages(const QString& path, const QString& fileName) 0452 { 0453 for (int i = 0 ; i < d->entries.size() ; ++i) 0454 { 0455 Entry& entry = d->entries[i]; 0456 0457 for (int e = 0 ; e < entry.referredImages.size() ; ++e) 0458 { 0459 HistoryImageId& id = entry.referredImages[e]; 0460 { 0461 if ((id.m_filePath == path) && (id.m_fileName == fileName)) 0462 { 0463 id.m_filePath.clear(); 0464 id.m_fileName.clear(); 0465 } 0466 } 0467 } 0468 } 0469 } 0470 0471 void DImageHistory::moveCurrentReferredImage(const QString& newPath, const QString& newFileName) 0472 { 0473 for (int i = 0 ; i < d->entries.size() ; ++i) 0474 { 0475 Entry& entry = d->entries[i]; 0476 0477 for (int e = 0 ; e < entry.referredImages.size() ; ++e) 0478 { 0479 HistoryImageId& id = entry.referredImages[e]; 0480 0481 if (id.isCurrentFile()) 0482 { 0483 id.setPath(newPath); 0484 id.setFileName(newFileName); 0485 } 0486 } 0487 } 0488 } 0489 0490 QString DImageHistory::toXml() const 0491 { 0492 QString xmlHistory; 0493 0494 QXmlStreamWriter stream(&xmlHistory); 0495 stream.setAutoFormatting(true); 0496 stream.writeStartDocument(); 0497 stream.writeStartElement(QLatin1String("history")); 0498 stream.writeAttribute(QLatin1String("version"), QString::number(1)); 0499 0500 for (int i = 0 ; i < entries().count() ; ++i) 0501 { 0502 const Entry& step = entries().at(i); 0503 0504 if (!step.action.isNull()) 0505 { 0506 stream.writeStartElement(QLatin1String("filter")); 0507 stream.writeAttribute(QLatin1String("filterName"), step.action.identifier()); 0508 stream.writeAttribute(QLatin1String("filterDisplayName"), step.action.displayableName()); 0509 stream.writeAttribute(QLatin1String("filterVersion"), QString::number(step.action.version())); 0510 0511 switch (step.action.category()) 0512 { 0513 case FilterAction::ReproducibleFilter: 0514 stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("reproducible")); 0515 break; 0516 0517 case FilterAction::ComplexFilter: 0518 stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("complex")); 0519 break; 0520 0521 case FilterAction::DocumentedHistory: 0522 stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("documentedHistory")); 0523 break; 0524 } 0525 0526 if (step.action.flags() & FilterAction::ExplicitBranch) 0527 { 0528 stream.writeAttribute(QLatin1String("branch"), QLatin1String("true")); 0529 } 0530 0531 stream.writeStartElement(QLatin1String("params")); 0532 0533 const QHash<QString,QVariant>& params = step.action.parameters(); 0534 0535 if (!params.isEmpty()) 0536 { 0537 QList<QString> keys = params.keys(); 0538 std::sort(keys.begin(), keys.end()); 0539 0540 Q_FOREACH (const QString& key, keys) 0541 { 0542 QHash<QString, QVariant>::const_iterator it; 0543 0544 for (it = params.find(key) ; it != params.end() && it.key() == key ; ++it) 0545 { 0546 stream.writeStartElement(QLatin1String("param")); 0547 stream.writeAttribute(QLatin1String("name"), key); 0548 stream.writeAttribute(QLatin1String("value"), it.value().toString()); 0549 stream.writeEndElement(); //param 0550 } 0551 } 0552 } 0553 0554 stream.writeEndElement(); //params 0555 stream.writeEndElement(); //filter 0556 } 0557 0558 if (!step.referredImages.isEmpty()) 0559 { 0560 Q_FOREACH (const HistoryImageId& imageId, step.referredImages) 0561 { 0562 if (!imageId.isValid()) 0563 { 0564 continue; 0565 } 0566 0567 if (imageId.isCurrentFile()) 0568 { 0569 continue; 0570 } 0571 0572 stream.writeStartElement(QLatin1String("file")); 0573 0574 if (!imageId.m_uuid.isNull()) 0575 { 0576 stream.writeAttribute(QLatin1String("uuid"), imageId.m_uuid); 0577 } 0578 0579 if (imageId.isOriginalFile()) 0580 { 0581 stream.writeAttribute(QLatin1String("type"), QLatin1String("original")); 0582 } 0583 else if (imageId.isSourceFile()) 0584 { 0585 stream.writeAttribute(QLatin1String("type"), QLatin1String("source")); 0586 } 0587 0588 stream.writeStartElement(QLatin1String("fileParams")); 0589 0590 if (!imageId.m_fileName.isNull()) 0591 { 0592 stream.writeAttribute(QLatin1String("fileName"), imageId.m_fileName); 0593 } 0594 0595 if (!imageId.m_filePath.isNull()) 0596 { 0597 stream.writeAttribute(QLatin1String("filePath"), imageId.m_filePath); 0598 } 0599 0600 if (!imageId.m_uniqueHash.isNull()) 0601 { 0602 stream.writeAttribute(QLatin1String("fileHash"), imageId.m_uniqueHash); 0603 } 0604 0605 if (imageId.m_fileSize) 0606 { 0607 stream.writeAttribute(QLatin1String("fileSize"), QString::number(imageId.m_fileSize)); 0608 } 0609 0610 if (imageId.isOriginalFile() && !imageId.m_creationDate.isNull()) 0611 { 0612 stream.writeAttribute(QLatin1String("creationDate"), imageId.m_creationDate.toString(Qt::ISODate)); 0613 } 0614 0615 stream.writeEndElement(); //fileParams 0616 0617 stream.writeEndElement(); //file 0618 } 0619 } 0620 } 0621 0622 stream.writeEndElement(); //history 0623 0624 stream.writeEndDocument(); 0625 0626 //qCDebug(DIGIKAM_DIMG_LOG) << xmlHistory; 0627 0628 return xmlHistory; 0629 } 0630 0631 DImageHistory DImageHistory::fromXml(const QString& xml) //DImageHistory 0632 { 0633 /* 0634 qCDebug(DIGIKAM_DIMG_LOG) << "Parsing image history XML"; 0635 */ 0636 0637 DImageHistory h; 0638 0639 if (xml.isEmpty()) 0640 { 0641 return h; 0642 } 0643 0644 QXmlStreamReader stream(xml); 0645 0646 if (!stream.readNextStartElement()) 0647 { 0648 return h; 0649 } 0650 0651 if (stream.name() != QLatin1String("history")) 0652 { 0653 return h; 0654 } 0655 0656 QString originalUUID; 0657 QDateTime originalCreationDate; 0658 0659 while (stream.readNextStartElement()) 0660 { 0661 if (stream.name() == QLatin1String("file")) 0662 { 0663 //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing file tag"; 0664 0665 HistoryImageId imageId(stream.attributes().value(QLatin1String("uuid")).toString()); 0666 0667 if (stream.attributes().value(QLatin1String("type")) == QLatin1String("original")) 0668 { 0669 imageId.m_type = HistoryImageId::Original; 0670 } 0671 else if (stream.attributes().value(QLatin1String("type")) == QLatin1String("source")) 0672 { 0673 imageId.m_type = HistoryImageId::Source; 0674 } 0675 else 0676 { 0677 imageId.m_type = HistoryImageId::Intermediate; 0678 } 0679 0680 while (stream.readNextStartElement()) 0681 { 0682 if (stream.name() == QLatin1String("fileParams")) 0683 { 0684 imageId.setFileName(stream.attributes().value(QLatin1String("fileName")).toString()); 0685 imageId.setPath(stream.attributes().value(QLatin1String("filePath")).toString()); 0686 QString date = stream.attributes().value(QLatin1String("creationDate")).toString(); 0687 0688 if (!date.isNull()) 0689 { 0690 imageId.setCreationDate(QDateTime::fromString(date, Qt::ISODate)); 0691 } 0692 0693 QString size = stream.attributes().value(QLatin1String("fileSize")).toString(); 0694 0695 if (stream.attributes().hasAttribute(QLatin1String("fileHash")) && !size.isNull()) 0696 { 0697 imageId.setUniqueHash(stream.attributes().value(QLatin1String("fileHash")).toString(), size.toInt()); 0698 } 0699 0700 stream.skipCurrentElement(); 0701 } 0702 else 0703 { 0704 stream.skipCurrentElement(); 0705 } 0706 } 0707 0708 if (imageId.isOriginalFile()) 0709 { 0710 originalUUID = imageId.m_uuid; 0711 originalCreationDate = imageId.m_creationDate; 0712 } 0713 else 0714 { 0715 imageId.m_originalUUID = originalUUID; 0716 0717 if (imageId.m_creationDate.isNull()) 0718 { 0719 imageId.m_creationDate = originalCreationDate; 0720 } 0721 } 0722 0723 if (imageId.isValid()) 0724 { 0725 h << imageId; 0726 } 0727 0728 } 0729 else if (stream.name() == QLatin1String("filter")) 0730 { 0731 /* 0732 qCDebug(DIGIKAM_DIMG_LOG) << "Parsing filter tag"; 0733 */ 0734 FilterAction::Category c = FilterAction::ComplexFilter; 0735 QStringView categoryString = stream.attributes().value(QLatin1String("filterCategory")); 0736 0737 if (categoryString == QLatin1String("reproducible")) 0738 { 0739 c = FilterAction::ReproducibleFilter; 0740 } 0741 else if (categoryString == QLatin1String("complex")) 0742 { 0743 c = FilterAction::ComplexFilter; 0744 } 0745 else if (categoryString == QLatin1String("documentedHistory")) 0746 { 0747 c = FilterAction::DocumentedHistory; 0748 } 0749 0750 FilterAction action(stream.attributes().value(QLatin1String("filterName")).toString(), 0751 stream.attributes().value(QLatin1String("filterVersion")).toString().toInt(), c); 0752 action.setDisplayableName(stream.attributes().value(QLatin1String("filterDisplayName")).toString()); 0753 0754 if (stream.attributes().value(QLatin1String("branch")) == QLatin1String("true")) 0755 { 0756 action.addFlag(FilterAction::ExplicitBranch); 0757 } 0758 0759 while (stream.readNextStartElement()) 0760 { 0761 if (stream.name() == QLatin1String("params")) 0762 { 0763 while (stream.readNextStartElement()) 0764 { 0765 if ((stream.name() == QLatin1String("param")) && 0766 stream.attributes().hasAttribute(QLatin1String("name"))) 0767 { 0768 action.addParameter(stream.attributes().value(QLatin1String("name")).toString(), 0769 stream.attributes().value(QLatin1String("value")).toString()); 0770 stream.skipCurrentElement(); 0771 } 0772 else 0773 { 0774 stream.skipCurrentElement(); 0775 } 0776 } 0777 0778 } 0779 else 0780 { 0781 stream.skipCurrentElement(); 0782 } 0783 } 0784 0785 h << action; 0786 } 0787 else 0788 { 0789 stream.skipCurrentElement(); 0790 } 0791 } 0792 0793 if (stream.hasError()) 0794 { 0795 // TODO: error handling 0796 0797 qCDebug(DIGIKAM_DIMG_LOG) << "An error occurred during parsing: " << stream.errorString(); 0798 } 0799 0800 /* 0801 qCDebug(DIGIKAM_DIMG_LOG) << "Parsing done"; 0802 */ 0803 return h; 0804 } 0805 0806 } // namespace digikam