File indexing completed on 2025-01-05 03:57:25
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-06-18 0007 * Description : class for determining new file name in terms of version management 0008 * 0009 * SPDX-FileCopyrightText: 2010-2011 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 "versionmanager.h" 0017 0018 // Qt includes 0019 0020 #include <QDir> 0021 #include <QUrl> 0022 0023 // KDE includes 0024 0025 #include <kconfiggroup.h> 0026 0027 // Local includes 0028 0029 #include "digikam_debug.h" 0030 #include "dimgfiltermanager.h" 0031 0032 namespace Digikam 0033 { 0034 0035 class Q_DECL_HIDDEN VersionNameCreator 0036 { 0037 public: 0038 0039 VersionNameCreator(const VersionFileInfo& loadedFile, 0040 const DImageHistory& m_resolvedInitialHistory, const DImageHistory& m_currentHistory, 0041 VersionManager* const q); 0042 0043 void checkNeedNewVersion(); 0044 void fork(); 0045 void setSaveDirectory(); 0046 void setSaveFormat(); 0047 void setSaveFileName(); 0048 void setSaveDirectory(const QString& path); 0049 void setSaveFormat(const QString& format); 0050 void setSaveFileName(const QString& name); 0051 void initOperation(); 0052 void checkIntermediates(); 0053 0054 protected: 0055 0056 VersionFileInfo nextIntermediate(const QString& format); 0057 void setFileSuffix(QString& fileName, const QString& format) const; 0058 void addFileSuffix(QString& baseName, const QString& format, const QString& originalName = QString()) const; 0059 0060 public: 0061 0062 VersionManagerSettings m_settings; 0063 0064 VersionFileInfo m_result; 0065 VersionFileInfo m_loadedFile; 0066 0067 VersionFileOperation m_operation; 0068 0069 const DImageHistory m_resolvedInitialHistory; 0070 const DImageHistory m_currentHistory; 0071 0072 bool m_fromRaw; 0073 bool m_newVersion; 0074 0075 QVariant m_version; 0076 QVariant m_intermediateCounter; 0077 QString m_baseName; 0078 QString m_intermediatePath; 0079 0080 VersionManager* const q; 0081 }; 0082 0083 VersionNameCreator::VersionNameCreator(const VersionFileInfo& loadedFile, 0084 const DImageHistory& m_resolvedInitialHistory, 0085 const DImageHistory& m_currentHistory, 0086 VersionManager* const q) 0087 : m_settings(q->settings()), 0088 m_loadedFile(loadedFile), 0089 m_resolvedInitialHistory(m_resolvedInitialHistory), 0090 m_currentHistory(m_currentHistory), 0091 m_fromRaw(false), 0092 m_newVersion(false), q(q) 0093 { 0094 m_loadedFile.format = m_loadedFile.format.toUpper(); 0095 m_fromRaw = (m_loadedFile.format.startsWith(QLatin1String("RAW"))); // also accept RAW-... format 0096 m_version = q->namingScheme()->initialCounter(); 0097 m_intermediateCounter = q->namingScheme()->initialCounter(); 0098 } 0099 0100 void VersionNameCreator::checkNeedNewVersion() 0101 { 0102 // First we check if we have any other files available. 0103 // The resolved initial history contains only referred files found in the collection 0104 // Note: The loaded file will have type Current 0105 0106 qCDebug(DIGIKAM_GENERAL_LOG) << m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original) 0107 << m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate) 0108 << m_fromRaw << q->workspaceFileFormats().contains(m_loadedFile.format); 0109 0110 if (!m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original) && 0111 !m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate)) 0112 { 0113 m_newVersion = true; 0114 } 0115 else if (m_fromRaw || !q->workspaceFileFormats().contains(m_loadedFile.format)) 0116 { 0117 // We check the loaded format: If it is not one of the workspace formats, or even raw, we need a new version 0118 m_newVersion = true; 0119 } 0120 else 0121 { 0122 m_newVersion = false; 0123 } 0124 } 0125 0126 void VersionNameCreator::fork() 0127 { 0128 m_newVersion = true; 0129 } 0130 0131 void VersionNameCreator::setSaveDirectory() 0132 { 0133 m_result.path = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName); 0134 m_intermediatePath = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName); 0135 } 0136 0137 void VersionNameCreator::setSaveDirectory(const QString& path) 0138 { 0139 m_result.path = path; 0140 m_intermediatePath = path; 0141 } 0142 0143 void VersionNameCreator::setSaveFormat() 0144 { 0145 m_result.format = m_settings.format; 0146 /* 0147 if (m_fromRaw) 0148 { 0149 m_result.format = m_settings.formatForStoringRAW; 0150 } 0151 else 0152 { 0153 m_result.format = m_settings.formatForStoringSubversions; 0154 } 0155 */ 0156 } 0157 0158 void VersionNameCreator::setSaveFormat(const QString& override) 0159 { 0160 m_result.format = override; 0161 } 0162 0163 void VersionNameCreator::setSaveFileName() 0164 { 0165 qCDebug(DIGIKAM_GENERAL_LOG) << "need new version" << m_newVersion; 0166 0167 VersionNamingScheme* const scheme = q->namingScheme(); 0168 0169 // initialize m_baseName, m_version, and m_intermediateCounter for intermediates 0170 0171 m_baseName = scheme->baseName(m_loadedFile.path, 0172 m_loadedFile.fileName, 0173 &m_version, 0174 &m_intermediateCounter); 0175 0176 qCDebug(DIGIKAM_GENERAL_LOG) << "analyzing file" << m_loadedFile.fileName << m_version << m_intermediateCounter; 0177 0178 // Update the result format for TIFF and JPG to the existing original suffix in short or long format. 0179 0180 int lastDot = m_loadedFile.fileName.lastIndexOf(QLatin1Char('.')); 0181 const bool tifFormat = (m_result.format == QLatin1String("TIFF")); 0182 const bool jpgFormat = (m_result.format == QLatin1String("JPG")); 0183 0184 if ((lastDot != -1) && (tifFormat || jpgFormat)) 0185 { 0186 int extSize = m_loadedFile.fileName.size() - lastDot - 1; 0187 QString suffix = m_loadedFile.fileName.right(extSize).toUpper(); 0188 0189 if (((suffix == QLatin1String("TIF")) && tifFormat) || 0190 ((suffix == QLatin1String("JPEG")) && jpgFormat)) 0191 { 0192 m_result.format = suffix; 0193 } 0194 } 0195 0196 if (!m_newVersion) 0197 { 0198 m_result.fileName = m_loadedFile.fileName; 0199 0200 if (m_loadedFile.format != m_result.format) 0201 { 0202 setFileSuffix(m_result.fileName, m_result.format); 0203 } 0204 } 0205 else 0206 { 0207 QDir dirInfo(m_result.path); 0208 0209 // To find the right number for the new version, go through all the items in the given dir, 0210 // the version number won't be bigger than count() 0211 0212 for (uint i = 0 ; i <= dirInfo.count() ; ++i) 0213 { 0214 QString suggestedName = scheme->versionFileName(m_result.path, m_baseName, m_version); 0215 0216 // Note: Always give a hard guarantee that the file does not exist 0217 0218 if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty()) 0219 { 0220 m_result.fileName = suggestedName; 0221 addFileSuffix(m_result.fileName, m_result.format, m_loadedFile.fileName); 0222 break; 0223 } 0224 0225 // increment for next attempt 0226 0227 m_version = scheme->incrementedCounter(m_version); 0228 } 0229 } 0230 } 0231 0232 void VersionNameCreator::setSaveFileName(const QString& fileName) 0233 { 0234 m_result.fileName = fileName; 0235 m_baseName = fileName.section(QLatin1Char('.'), 0, 0); 0236 0237 // m_version remains unknown 0238 } 0239 0240 void VersionNameCreator::initOperation() 0241 { 0242 m_operation.loadedFile = m_loadedFile; 0243 m_operation.saveFile = m_result; 0244 0245 if (m_newVersion) 0246 { 0247 m_operation.tasks |= VersionFileOperation::NewFile; 0248 } 0249 else 0250 { 0251 if (m_result.fileName == m_loadedFile.fileName) 0252 { 0253 m_operation.tasks |= VersionFileOperation::Replace; 0254 } 0255 else 0256 { 0257 m_operation.tasks |= VersionFileOperation::SaveAndDelete; 0258 } 0259 } 0260 } 0261 0262 void VersionNameCreator::checkIntermediates() 0263 { 0264 // call when task has been determined 0265 0266 qCDebug(DIGIKAM_GENERAL_LOG) << "Will replace" << bool(m_operation.tasks & VersionFileOperation::Replace) 0267 << "save after each session" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) 0268 << "save after raw" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion) 0269 << "save when not repro" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible); 0270 0271 if ((m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) && 0272 (m_operation.tasks & VersionFileOperation::Replace)) 0273 { 0274 // We want a snapshot after each session. The main file will be overwritten by the current state. 0275 // So we consider the current file as snapshot of the last session and move 0276 // it to an intermediate before it is overwritten 0277 0278 m_operation.tasks |= VersionFileOperation::MoveToIntermediate; 0279 m_operation.intermediateForLoadedFile = nextIntermediate(m_loadedFile.format); 0280 0281 // this amounts to storing firstStep - 1 0282 } 0283 0284 // These are, inclusively, the first and last step the state after which we may have to store. 0285 // m_resolvedInitialHistory.size() - 1 is the loaded file 0286 // m_currentHistory.size() - 1 is the current version 0287 0288 int firstStep = m_resolvedInitialHistory.size(); 0289 int lastStep = m_currentHistory.size() - 2; // index of last but one entry 0290 0291 qCDebug(DIGIKAM_GENERAL_LOG) << "initial history" << m_resolvedInitialHistory.size() 0292 << "current history" << m_currentHistory.size() 0293 << "first step" << firstStep << "last step" << lastStep; 0294 0295 if (lastStep < firstStep) 0296 { 0297 // only a single editing step, or even "backwards" in history (possible with redo) 0298 return; 0299 } 0300 0301 if (!firstStep) 0302 { 0303 qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid history: resolved initial history has no entries"; 0304 firstStep = 1; 0305 } 0306 0307 if (m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion) 0308 { 0309 int rawConversionStep = -1; 0310 0311 for (int i = firstStep ; i <= lastStep ; ++i) 0312 { 0313 if (DImgFilterManager::instance()->isRawConversion(m_currentHistory.action(i).identifier())) 0314 { 0315 rawConversionStep = i; 0316 // break? multiple raw conversion steps?? 0317 } 0318 } 0319 0320 if (rawConversionStep != -1) 0321 { 0322 m_operation.intermediates.insert(rawConversionStep, VersionFileInfo()); 0323 } 0324 } 0325 0326 if (m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible) 0327 { 0328 for (int i = firstStep ; i <= lastStep ; ++i) 0329 { 0330 qCDebug(DIGIKAM_GENERAL_LOG) << "step" << i 0331 << "is reproducible" 0332 << (m_currentHistory.action(i).category() == FilterAction::ReproducibleFilter); 0333 0334 switch (m_currentHistory.action(i).category()) 0335 { 0336 case FilterAction::ReproducibleFilter: 0337 break; 0338 0339 case FilterAction::ComplexFilter: 0340 case FilterAction::DocumentedHistory: 0341 m_operation.intermediates.insert(i, VersionFileInfo()); 0342 break; 0343 } 0344 } 0345 } 0346 0347 qCDebug(DIGIKAM_GENERAL_LOG) << "Save intermediates after steps" << m_operation.intermediates.keys(); 0348 0349 if (!m_operation.intermediates.isEmpty()) 0350 { 0351 m_operation.tasks |= VersionFileOperation::StoreIntermediates; 0352 0353 // now all steps are available, already ordered thanks to QMap. Just fill in the empty VersionFileInfos. 0354 0355 QMap<int,VersionFileInfo>::iterator it; 0356 0357 for (it = m_operation.intermediates.begin() ; it != m_operation.intermediates.end() ; ++it) 0358 { 0359 it.value() = nextIntermediate(m_result.format); 0360 } 0361 } 0362 } 0363 0364 VersionFileInfo VersionNameCreator::nextIntermediate(const QString& format) 0365 { 0366 VersionNamingScheme* const scheme = q->namingScheme(); 0367 0368 VersionFileInfo intermediate; 0369 intermediate.path = m_intermediatePath; 0370 intermediate.format = format; 0371 0372 QDir dirInfo(m_intermediatePath); 0373 0374 for (uint i = 0 ; i <= dirInfo.count() ; ++i) 0375 { 0376 QString suggestedName = scheme->intermediateFileName(m_intermediatePath, m_baseName, 0377 m_version, m_intermediateCounter); 0378 0379 // it is important to increment before returning - we may have to produce a number of files 0380 0381 m_intermediateCounter = scheme->incrementedCounter(m_intermediateCounter); 0382 0383 // Note: Always give a hard guarantee that the file does not exist 0384 0385 if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty()) 0386 { 0387 intermediate.fileName = suggestedName; 0388 setFileSuffix(intermediate.fileName, format); 0389 break; 0390 } 0391 } 0392 0393 return intermediate; 0394 } 0395 0396 void VersionNameCreator::setFileSuffix(QString& fileName, const QString& format) const 0397 { 0398 if (fileName.isEmpty()) 0399 { 0400 return; 0401 } 0402 0403 // QFileInfo uses the same string manipulation 0404 0405 int lastDot = fileName.lastIndexOf(QLatin1Char('.')); 0406 bool isLower = true; 0407 0408 if (lastDot == -1) 0409 { 0410 fileName += QLatin1Char('.'); 0411 lastDot = fileName.size() - 1; 0412 } 0413 else 0414 { 0415 isLower = fileName.at(fileName.size() - 1).isLower(); 0416 } 0417 0418 int suffixBegin = lastDot + 1; 0419 0420 if (fileName.mid(suffixBegin).compare(format, Qt::CaseInsensitive)) 0421 { 0422 fileName.replace(suffixBegin, fileName.size() - suffixBegin, isLower ? format.toLower() : format); 0423 } 0424 } 0425 0426 void VersionNameCreator::addFileSuffix(QString& fileName, const QString& format, const QString& originalName) const 0427 { 0428 if (fileName.isEmpty()) 0429 { 0430 return; 0431 } 0432 0433 bool isLower = true; 0434 0435 if (!originalName.isEmpty()) 0436 { 0437 // if original name had upper case suffix, continue using upper case 0438 0439 isLower = originalName.at(originalName.size() - 1).isLower(); 0440 } 0441 0442 if (!fileName.endsWith(QLatin1Char('.'))) 0443 { 0444 fileName += QLatin1Char('.'); 0445 } 0446 0447 fileName += (isLower ? format.toLower() : format); 0448 } 0449 0450 // ------------------------------------------------------------------------------------------------------ 0451 0452 class Q_DECL_HIDDEN VersionManager::VersionManagerPriv 0453 { 0454 public: 0455 0456 VersionManagerPriv() 0457 { 0458 scheme = nullptr; 0459 } 0460 0461 VersionManagerSettings settings; 0462 VersionNamingScheme* scheme; 0463 0464 DefaultVersionNamingScheme defaultScheme; 0465 }; 0466 0467 // ------------------------------------------------------------------------------------------------------ 0468 0469 VersionManager::VersionManager() 0470 : d(new VersionManagerPriv) 0471 { 0472 } 0473 0474 VersionManager::~VersionManager() 0475 { 0476 delete d->scheme; 0477 delete d; 0478 } 0479 0480 void VersionManager::setSettings(const VersionManagerSettings& settings) 0481 { 0482 d->settings = settings; 0483 } 0484 0485 VersionManagerSettings VersionManager::settings() const 0486 { 0487 return d->settings; 0488 } 0489 0490 void VersionManager::setNamingScheme(VersionNamingScheme* scheme) 0491 { 0492 d->scheme = scheme; 0493 } 0494 0495 VersionNamingScheme* VersionManager::namingScheme() const 0496 { 0497 if (d->scheme) 0498 { 0499 return d->scheme; 0500 } 0501 else 0502 { 0503 return &d->defaultScheme; 0504 } 0505 } 0506 0507 VersionFileOperation VersionManager::operation(FileNameType request, const VersionFileInfo& loadedFile, 0508 const DImageHistory& initialResolvedHistory, 0509 const DImageHistory& currentHistory) 0510 { 0511 VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); 0512 0513 if (request == CurrentVersionName) 0514 { 0515 name.checkNeedNewVersion(); 0516 } 0517 else if (request == NewVersionName) 0518 { 0519 name.fork(); 0520 } 0521 0522 name.setSaveDirectory(); 0523 name.setSaveFormat(); 0524 name.setSaveFileName(); 0525 name.initOperation(); 0526 name.checkIntermediates(); 0527 0528 return name.m_operation; 0529 } 0530 0531 VersionFileOperation VersionManager::operationNewVersionInFormat(const VersionFileInfo& loadedFile, 0532 const QString& format, 0533 const DImageHistory& initialResolvedHistory, 0534 const DImageHistory& currentHistory) 0535 { 0536 VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); 0537 0538 name.fork(); 0539 name.setSaveDirectory(); 0540 name.setSaveFormat(format); 0541 name.setSaveFileName(); 0542 name.initOperation(); 0543 name.checkIntermediates(); 0544 0545 return name.m_operation; 0546 } 0547 0548 VersionFileOperation VersionManager::operationNewVersionAs(const VersionFileInfo& loadedFile, 0549 const VersionFileInfo& saveLocation, 0550 const DImageHistory& initialResolvedHistory, 0551 const DImageHistory& currentHistory) 0552 { 0553 VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); 0554 0555 name.fork(); 0556 name.setSaveDirectory(saveLocation.path); 0557 name.setSaveFormat(saveLocation.format); 0558 name.setSaveFileName(saveLocation.fileName); 0559 name.initOperation(); 0560 name.checkIntermediates(); 0561 0562 return name.m_operation; 0563 } 0564 0565 QString VersionManager::toplevelDirectory(const QString& path) 0566 { 0567 Q_UNUSED(path); 0568 0569 return QLatin1String("/"); 0570 } 0571 0572 QStringList VersionManager::workspaceFileFormats() const 0573 { 0574 QStringList formats; 0575 formats << QLatin1String("JPG") << QLatin1String("PNG") << QLatin1String("TIFF") << QLatin1String("PGF") << QLatin1String("JP2"); 0576 QString f = d->settings.format.toUpper(); 0577 0578 if (!formats.contains(f)) 0579 { 0580 formats << f; 0581 } 0582 /* 0583 f = d->settings.formatForStoringSubversions.toUpper(); 0584 0585 if (!formats.contains(f)) 0586 { 0587 formats << f; 0588 } 0589 */ 0590 return formats; 0591 } 0592 0593 } // namespace Digikam