File indexing completed on 2024-05-19 04:56:01
0001 /** 0002 * \file dirrenamer.cpp 0003 * Directory renamer. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 23 Jul 2011 0008 * 0009 * Copyright (C) 2011-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "dirrenamer.h" 0028 #include <QFileInfo> 0029 #include <QDir> 0030 #include <QCoreApplication> 0031 #include "trackdata.h" 0032 #include "saferename.h" 0033 #include "taggedfilesystemmodel.h" 0034 #include "modeliterator.h" 0035 #include "formatconfig.h" 0036 0037 /** 0038 * Data collected by DirNameFormatReplacer during a rename session. 0039 */ 0040 class DirNameFormatReplacerContext { 0041 public: 0042 /** 0043 * Store value for aggregate function. 0044 * @param code aggregating code, e.g. "max-year" 0045 * @param value value of base code (e.g. "year") 0046 */ 0047 void addValue(const QString& code, const QString& value); 0048 0049 /** 0050 * Register the replaced directory name which still contains 0051 * placeholders for the aggregate codes. 0052 * @param dirName directory name with replacements and aggregate codes, 0053 * QString() to terminate the rename session 0054 */ 0055 void putDirName(const QString& dirName); 0056 0057 /** 0058 * Get and clear the replacements for all the replacement codes 0059 * encountered during this rename session. 0060 * Shall be called at the end of the rename session 0061 * @return list of (directory with aggregate codes, 0062 * directory with replaced aggregate codes) pairs. 0063 */ 0064 QList<QPair<QString, QString>> takeReplacements(); 0065 0066 /** 0067 * Check if aggregated codes are used. 0068 * @return true if aggregated codes have been added using addValue(). 0069 */ 0070 bool hasAggregatedCodes() const { return !m_aggregatedCodes.isEmpty(); } 0071 0072 private: 0073 QString getAggregate(const QString& code) const; 0074 0075 QList<QPair<QString, QString>> m_replacements; 0076 QHash<QString, QStringList> m_currentCodes; 0077 QHash<QString, QStringList> m_aggregatedCodes; 0078 QString m_aggregatedDirName; 0079 }; 0080 0081 void DirNameFormatReplacerContext::addValue(const QString& code, 0082 const QString& value) 0083 { 0084 m_currentCodes[code].append(value); 0085 } 0086 0087 void DirNameFormatReplacerContext::putDirName(const QString& dirName) 0088 { 0089 if (m_aggregatedDirName.isEmpty()) { 0090 // First directory name, start aggregation. 0091 m_aggregatedDirName = dirName; 0092 m_aggregatedCodes = m_currentCodes; 0093 } else if (m_aggregatedDirName != dirName) { 0094 // New directory name, replace aggregated values and return result. 0095 QString replacedDirName = m_aggregatedDirName; 0096 for (auto it = m_aggregatedCodes.constBegin(); 0097 it != m_aggregatedCodes.constEnd(); 0098 ++it) { 0099 replacedDirName.replace(it.key(), getAggregate(it.key())); 0100 } 0101 if (replacedDirName != m_aggregatedDirName) { 0102 m_replacements.append({m_aggregatedDirName, replacedDirName}); 0103 } 0104 m_aggregatedCodes = m_currentCodes; 0105 m_aggregatedDirName = dirName; 0106 } else { 0107 // Still the same directory name, keep on aggregating. 0108 for (auto it = m_currentCodes.constBegin(); 0109 it != m_currentCodes.constEnd(); 0110 ++it) { 0111 m_aggregatedCodes[it.key()].append(it.value()); 0112 } 0113 } 0114 m_currentCodes.clear(); 0115 } 0116 0117 QList<QPair<QString, QString>> DirNameFormatReplacerContext::takeReplacements() 0118 { 0119 // Terminate current directory aggregation. 0120 putDirName(QString()); 0121 QList<QPair<QString, QString>> result; 0122 m_replacements.swap(result); 0123 return result; 0124 } 0125 0126 QString DirNameFormatReplacerContext::getAggregate(const QString& code) const 0127 { 0128 QString result; 0129 const QStringList values = m_aggregatedCodes.value(code); 0130 if (code.startsWith(QLatin1String("max-"))) { 0131 for (const QString& value : values) { 0132 if (value > result) { 0133 result = value; 0134 } 0135 } 0136 } else if (code.startsWith(QLatin1String("min-"))) { 0137 for (const QString& value : values) { 0138 if (result.isNull() || value < result) { 0139 result = value; 0140 } 0141 } 0142 } else if (code.startsWith(QLatin1String("unq-"))) { 0143 for (const QString& value : values) { 0144 if (result.isNull()) { 0145 result = value; 0146 } else if (value != result) { 0147 result.clear(); 0148 break; 0149 } 0150 } 0151 } 0152 return result; 0153 } 0154 0155 0156 namespace { 0157 0158 /** 0159 * Specialized track data format replacer using context to support 0160 * aggregate functions. 0161 */ 0162 class DirNameFormatReplacer : public TrackDataFormatReplacer { 0163 public: 0164 /** 0165 * Constructor. 0166 * @param context context to store aggregate data 0167 * @param trackData track data 0168 * @param str string with format codes 0169 */ 0170 explicit DirNameFormatReplacer( 0171 DirNameFormatReplacerContext& context, 0172 const TrackData& trackData, 0173 const QString& str = QString()); 0174 0175 ~DirNameFormatReplacer() override = default; 0176 0177 DirNameFormatReplacer(const DirNameFormatReplacer& other) = delete; 0178 DirNameFormatReplacer &operator=(const DirNameFormatReplacer& other) = delete; 0179 0180 protected: 0181 QString getReplacement(const QString& code) const override; 0182 0183 private: 0184 DirNameFormatReplacerContext& m_context; 0185 }; 0186 0187 DirNameFormatReplacer::DirNameFormatReplacer( 0188 DirNameFormatReplacerContext& context, 0189 const TrackData& trackData, 0190 const QString& str) 0191 : TrackDataFormatReplacer(trackData, str), m_context(context) 0192 { 0193 } 0194 0195 QString DirNameFormatReplacer::getReplacement(const QString& code) const 0196 { 0197 if (code.startsWith(QLatin1String("max-")) || 0198 code.startsWith(QLatin1String("min-")) || 0199 code.startsWith(QLatin1String("unq-"))) { 0200 QString value = TrackDataFormatReplacer::getReplacement(code.mid(4)); 0201 m_context.addValue(code, value); 0202 return code; 0203 } 0204 return TrackDataFormatReplacer::getReplacement(code); 0205 } 0206 0207 0208 /** 0209 * Get parent directory. 0210 * 0211 * @param dir directory 0212 * 0213 * @return parent directory (terminated by separator), 0214 * empty string if no separator in dir. 0215 */ 0216 QString parentDirectory(const QString& dir) 0217 { 0218 QString parent(dir); 0219 if (int slashPos = parent.lastIndexOf(QLatin1Char('/')); slashPos != -1) { 0220 parent.truncate(slashPos + 1); 0221 } else { 0222 parent = QLatin1String(""); 0223 } 0224 return parent; 0225 } 0226 0227 } 0228 0229 /** 0230 * Constructor. 0231 * @param parent parent object 0232 */ 0233 DirRenamer::DirRenamer(QObject* parent) : QObject(parent), 0234 m_fmtContext(new DirNameFormatReplacerContext), 0235 m_tagVersion(Frame::TagVAll), m_aborted(false), m_actionCreate(false) 0236 { 0237 setObjectName(QLatin1String("DirRenamer")); 0238 } 0239 0240 /** 0241 * Destructor. 0242 */ 0243 DirRenamer::~DirRenamer() 0244 { 0245 delete m_fmtContext; 0246 } 0247 0248 /** Only defined for generation of translation files */ 0249 #define CREATE_DIR_FAILED_FOR_PO QT_TRANSLATE_NOOP("@default", "Create folder %1 failed\n") 0250 0251 /** 0252 * Create a directory if it does not exist. 0253 * 0254 * @param dir directory path 0255 * @param index model index of item to rename 0256 * @param errorMsg if not NULL and an error occurred, a message is appended here, 0257 * otherwise it is not touched 0258 * 0259 * @return true if directory exists or was created successfully. 0260 */ 0261 bool DirRenamer::createDirectory( 0262 const QString& dir, const QPersistentModelIndex& index, 0263 QString* errorMsg) const 0264 { 0265 if (auto model = const_cast<TaggedFileSystemModel*>( 0266 qobject_cast<const TaggedFileSystemModel*>(index.model()))) { 0267 const QString parentDirName = model->filePath(index.parent()); 0268 if (const QString relativeName = QDir(parentDirName).relativeFilePath(dir); 0269 model->mkdir(index.parent(), relativeName).isValid() && 0270 QFileInfo(dir).isDir()) { 0271 return true; 0272 } 0273 } 0274 if (QFileInfo(dir).isDir() || 0275 (QDir().mkdir(dir) && QFileInfo(dir).isDir())) { 0276 return true; 0277 } 0278 if (errorMsg) { 0279 errorMsg->append(tr("Create folder %1 failed\n").arg(dir)); 0280 } 0281 return false; 0282 } 0283 0284 /** Only defined for generation of translation files */ 0285 #define FILE_ALREADY_EXISTS_FOR_PO QT_TRANSLATE_NOOP("@default", "File %1 already exists\n") 0286 /** Only defined for generation of translation files */ 0287 #define IS_NOT_DIR_FOR_PO QT_TRANSLATE_NOOP("@default", "%1 is not a folder\n") 0288 /** Only defined for generation of translation files */ 0289 #define RENAME_FAILED_FOR_PO QT_TRANSLATE_NOOP("@default", "Rename %1 to %2 failed\n") 0290 0291 /** 0292 * Rename a directory. 0293 * 0294 * @param olddir old directory name 0295 * @param newdir new directory name 0296 * @param index model index of item to rename 0297 * @param errorMsg if not NULL and an error occurred, a message is 0298 * appended here, otherwise it is not touched 0299 * 0300 * @return true if rename successful. 0301 */ 0302 bool DirRenamer::renameDirectory( 0303 const QString& olddir, const QString& newdir, 0304 const QPersistentModelIndex& index, QString* errorMsg) const 0305 { 0306 if (QFileInfo::exists(newdir)) { 0307 if (errorMsg) { 0308 errorMsg->append(tr("File %1 already exists\n").arg(newdir)); 0309 } 0310 return false; 0311 } 0312 if (!QFileInfo(olddir).isDir()) { 0313 if (errorMsg) { 0314 errorMsg->append(tr("%1 is not a folder\n").arg(olddir)); 0315 } 0316 return false; 0317 } 0318 if (index.isValid()) { 0319 // The directory must be closed before renaming on Windows. 0320 TaggedFileIterator::closeFileHandles(index); 0321 } 0322 if (auto model = const_cast<TaggedFileSystemModel*>( 0323 qobject_cast<const TaggedFileSystemModel*>(index.model()))) { 0324 const QString parentDirName = model->filePath(index.parent()); 0325 if (const QString relativeName = QDir(parentDirName).relativeFilePath(newdir); 0326 model->rename(index, relativeName) && QFileInfo(newdir).isDir()) { 0327 return true; 0328 } 0329 } 0330 if (Utils::safeRename(olddir, newdir) && QFileInfo(newdir).isDir()) { 0331 return true; 0332 } 0333 if (errorMsg) { 0334 errorMsg->append(tr("Rename %1 to %2 failed\n").arg(olddir, newdir)); 0335 } 0336 return false; 0337 } 0338 0339 /** Only defined for generation of translation files */ 0340 #define ALREADY_EXISTS_FOR_PO QT_TRANSLATE_NOOP("@default", "%1 already exists\n") 0341 /** Only defined for generation of translation files */ 0342 #define IS_NOT_FILE_FOR_PO QT_TRANSLATE_NOOP("@default", "%1 is not a file\n") 0343 0344 /** 0345 * Rename a file. 0346 * 0347 * @param oldfn old file name 0348 * @param newfn new file name 0349 * @param errorMsg if not NULL and an error occurred, a message is 0350 * appended here, otherwise it is not touched 0351 * @param index model index of item to rename 0352 * 0353 * @return true if rename successful or newfn already exists. 0354 */ 0355 bool DirRenamer::renameFile(const QString& oldfn, const QString& newfn, 0356 const QPersistentModelIndex& index, QString* errorMsg) const 0357 { 0358 if (QFileInfo(newfn).isFile()) { 0359 return true; 0360 } 0361 if (QFileInfo::exists(newfn)) { 0362 if (errorMsg) { 0363 errorMsg->append(tr("%1 already exists\n").arg(newfn)); 0364 } 0365 return false; 0366 } 0367 if (!QFileInfo(oldfn).isFile()) { 0368 if (errorMsg) { 0369 errorMsg->append(tr("%1 is not a file\n").arg(oldfn)); 0370 } 0371 return false; 0372 } 0373 if (TaggedFile* taggedFile = 0374 TaggedFileSystemModel::getTaggedFileOfIndex(index)) { 0375 // The file must be closed before renaming on Windows. 0376 taggedFile->closeFileHandle(); 0377 } 0378 if (Utils::safeRename(oldfn, newfn) && QFileInfo(newfn).isFile()) { 0379 return true; 0380 } 0381 if (errorMsg) { 0382 errorMsg->append(tr("Rename %1 to %2 failed\n").arg(oldfn, newfn)); 0383 } 0384 return false; 0385 } 0386 0387 /** 0388 * Generate new directory name according to current settings. 0389 * 0390 * @param taggedFile file to get information from 0391 * @param olddir pointer to QString to place old directory name into, 0392 * NULL if not used 0393 * 0394 * @return new directory name. 0395 */ 0396 QString DirRenamer::generateNewDirname(TaggedFile* taggedFile, QString* olddir) 0397 { 0398 taggedFile->readTags(false); 0399 TrackData trackData(*taggedFile, m_tagVersion); 0400 QString newdir(taggedFile->getDirname()); 0401 #ifdef Q_OS_WIN32 0402 newdir.replace(QLatin1Char('\\'), QLatin1Char('/')); 0403 #endif 0404 if (newdir.endsWith(QLatin1Char('/'))) { 0405 // remove trailing separator 0406 newdir.truncate(newdir.length() - 1); 0407 } 0408 if (olddir) { 0409 *olddir = newdir; 0410 } 0411 if (!trackData.isEmptyOrInactive()) { 0412 if (!m_actionCreate) { 0413 newdir = parentDirectory(newdir); 0414 } else if (!newdir.isEmpty()) { 0415 newdir.append(QLatin1Char('/')); 0416 } 0417 DirNameFormatReplacer fmt(*m_fmtContext, trackData, m_format); 0418 fmt.replacePercentCodes(FormatReplacer::FSF_ReplaceSeparators); 0419 QString baseName = fmt.getString(); 0420 if (FormatConfig& fnCfg = FilenameFormatConfig::instance(); 0421 fnCfg.useForOtherFileNames()) { 0422 bool isFilenameFormatter = fnCfg.switchFilenameFormatter(false); 0423 if (!baseName.contains(QLatin1Char('/'))) { 0424 fnCfg.formatString(baseName); 0425 } else { 0426 // If the new folder name contains multiple path components separated 0427 // by '/', make sure not to replace the '/' when applying the format. 0428 QStringList baseNameComponents = baseName.split(QLatin1Char('/')); 0429 for (auto it = baseNameComponents.begin(); 0430 it != baseNameComponents.end(); 0431 ++it) { 0432 fnCfg.formatString(*it); 0433 } 0434 baseName = baseNameComponents.join(QLatin1Char('/')); 0435 } 0436 fnCfg.switchFilenameFormatter(isFilenameFormatter); 0437 } 0438 m_fmtContext->putDirName(baseName); 0439 newdir.append( 0440 FilenameFormatConfig::instance().joinFileName(baseName, QString())); 0441 } 0442 return newdir; 0443 } 0444 0445 /** 0446 * Clear the rename actions. 0447 * This method has to be called before scheduling new actions. 0448 */ 0449 void DirRenamer::clearActions() 0450 { 0451 m_actions.clear(); 0452 } 0453 0454 /** 0455 * Add a rename action. 0456 * 0457 * @param type type of action 0458 * @param src source file or directory name 0459 * @param dest destination file or directory name 0460 * @param index model index of item to rename 0461 */ 0462 void DirRenamer::addAction(RenameAction::Type type, const QString& src, const QString& dest, 0463 const QPersistentModelIndex& index) 0464 { 0465 // do not add an action if the source or destination is already in an action 0466 for (auto it = m_actions.constBegin(); it != m_actions.constEnd(); ++it) { 0467 if ((!src.isEmpty() && it->m_src == src) || 0468 (!dest.isEmpty() && it->m_dest == dest)){ 0469 return; 0470 } 0471 } 0472 0473 RenameAction action(type, src, dest, index); 0474 m_actions.append(action); 0475 if (!m_fmtContext->hasAggregatedCodes()) { 0476 emit actionScheduled(describeAction(action)); 0477 } 0478 } 0479 0480 /** 0481 * Add a rename action. 0482 * 0483 * @param type type of action 0484 * @param dest destination file or directory name 0485 */ 0486 void DirRenamer::addAction(RenameAction::Type type, const QString& dest) 0487 { 0488 addAction(type, QString(), dest); 0489 } 0490 0491 /** 0492 * Check if there is already an action scheduled for this source. 0493 * 0494 * @return true if a rename action for the source exists. 0495 */ 0496 bool DirRenamer::actionHasSource(const QString& src) const 0497 { 0498 if (src.isEmpty()) { 0499 return false; 0500 } 0501 for (auto it = m_actions.constBegin(); it != m_actions.constEnd(); ++it) { 0502 if (it->m_src == src) { 0503 return true; 0504 } 0505 } 0506 return false; 0507 } 0508 0509 /** 0510 * Check if there is already an action scheduled for this destination. 0511 * 0512 * @return true if a rename or create action for the destination exists. 0513 */ 0514 bool DirRenamer::actionHasDestination(const QString& dest) const 0515 { 0516 if (dest.isEmpty()) { 0517 return false; 0518 } 0519 for (auto it = m_actions.constBegin(); it != m_actions.constEnd(); ++it) { 0520 if (it->m_dest == dest) { 0521 return true; 0522 } 0523 } 0524 return false; 0525 } 0526 0527 /** 0528 * Replace directory name if there is already a rename action. 0529 * 0530 * @param src directory name, will be replaced if there is a rename action 0531 */ 0532 void DirRenamer::replaceIfAlreadyRenamed(QString& src) const 0533 { 0534 bool found = true; 0535 for (int i = 0; found && i < 5; ++i) { 0536 found = false; 0537 for (auto it = m_actions.constBegin(); it != m_actions.constEnd(); ++it) { 0538 if (it->m_type == RenameAction::RenameDirectory && 0539 it->m_src == src) { 0540 src = it->m_dest; 0541 found = true; 0542 break; 0543 } 0544 } 0545 } 0546 } 0547 0548 /** 0549 * Schedule the actions necessary to rename the directory containing a file. 0550 * 0551 * @param taggedFile file in directory 0552 */ 0553 void DirRenamer::scheduleAction(TaggedFile* taggedFile) 0554 { 0555 QString currentDirname; 0556 QString newDirname(generateNewDirname(taggedFile, ¤tDirname)); 0557 bool again = false; 0558 for (int round = 0; round < 2; ++round) { 0559 replaceIfAlreadyRenamed(currentDirname); 0560 if (newDirname != currentDirname) { 0561 if (newDirname.startsWith(currentDirname + QLatin1Char('/'))) { 0562 // A new directory is created in the current directory. 0563 bool createDir = true; 0564 QString dirWithFiles(currentDirname); 0565 for (int i = 0; 0566 createDir && newDirname.startsWith(currentDirname) && i < 5; 0567 i++) { 0568 QString newPart(newDirname.mid(currentDirname.length())); 0569 // currentDirname does not end with a separator, so newPart 0570 // starts with a separator and the search starts with the 0571 // second character. 0572 if (int slashPos = newPart.indexOf(QLatin1Char('/'), 1); 0573 slashPos != -1 && slashPos != newPart.length() - 1) { 0574 newPart.truncate(slashPos); 0575 // the new part has multiple directories 0576 // => create one directory 0577 } else { 0578 createDir = false; 0579 } 0580 // Create a directory for each file and move it. 0581 addAction(RenameAction::CreateDirectory, QString(), 0582 currentDirname + newPart, taggedFile->getIndex()); 0583 if (!createDir) { 0584 addAction(RenameAction::RenameFile, 0585 dirWithFiles + QLatin1Char('/') + taggedFile->getFilename(), 0586 currentDirname + newPart + QLatin1Char('/') + taggedFile->getFilename(), 0587 taggedFile->getIndex()); 0588 } 0589 currentDirname = currentDirname + newPart; 0590 } 0591 } else { 0592 if (QString parent(parentDirectory(currentDirname)); 0593 newDirname.startsWith(parent)) { 0594 QString newPart(newDirname.mid(parent.length())); 0595 if (int slashPos = newPart.indexOf(QLatin1Char('/')); 0596 slashPos != -1 && slashPos != newPart.length() - 1) { 0597 newPart.truncate(slashPos); 0598 // the new part has multiple directories 0599 // => rename current directory, then create additional 0600 // directories. 0601 again = true; 0602 } 0603 if (QString parentWithNewPart = parent + newPart; 0604 (QFileInfo(parentWithNewPart).isDir() && 0605 !actionHasSource(parentWithNewPart)) || 0606 actionHasDestination(parentWithNewPart)) { 0607 // directory already exists => move files 0608 addAction(RenameAction::RenameFile, 0609 currentDirname + QLatin1Char('/') + taggedFile->getFilename(), 0610 parentWithNewPart + QLatin1Char('/') + taggedFile->getFilename(), 0611 taggedFile->getIndex()); 0612 currentDirname = parentWithNewPart; 0613 } else { 0614 addAction(RenameAction::RenameDirectory, currentDirname, parentWithNewPart, 0615 taggedFile->getIndex().parent()); 0616 currentDirname = parentWithNewPart; 0617 } 0618 } else { 0619 // new directory name is too different 0620 addAction(RenameAction::ReportError, tr("New folder name is too different\n")); 0621 } 0622 } 0623 } 0624 if (!again) break; 0625 } 0626 } 0627 0628 /** 0629 * Terminate scheduling of actions. 0630 */ 0631 void DirRenamer::endScheduleActions() 0632 { 0633 if (m_fmtContext->hasAggregatedCodes()) { 0634 const auto replacements = m_fmtContext->takeReplacements(); 0635 for (RenameAction& action : m_actions) { 0636 for (const auto& replacement : replacements) { 0637 action.m_src.replace(replacement.first, replacement.second); 0638 action.m_dest.replace(replacement.first, replacement.second); 0639 } 0640 emit actionScheduled(describeAction(action)); 0641 } 0642 } 0643 } 0644 0645 /** 0646 * Perform the scheduled rename actions. 0647 * 0648 * @param errorMsg if not 0 and an error occurred, a message is appended here, 0649 * otherwise it is not touched 0650 */ 0651 void DirRenamer::performActions(QString* errorMsg) 0652 { 0653 for (auto it = m_actions.constBegin(); it != m_actions.constEnd(); ++it) { 0654 switch (it->m_type) { 0655 case RenameAction::CreateDirectory: 0656 createDirectory(it->m_dest, it->m_index, errorMsg); 0657 break; 0658 case RenameAction::RenameDirectory: 0659 if (renameDirectory(it->m_src, it->m_dest, it->m_index, 0660 errorMsg)) { 0661 if (it->m_src == m_dirName) { 0662 m_dirName = it->m_dest; 0663 } 0664 } 0665 break; 0666 case RenameAction::RenameFile: 0667 renameFile(it->m_src, it->m_dest, it->m_index, errorMsg); 0668 break; 0669 case RenameAction::ReportError: 0670 default: 0671 if (errorMsg) { 0672 *errorMsg += it->m_dest; 0673 } 0674 } 0675 } 0676 } 0677 0678 /** 0679 * Get description of an actions to be performed. 0680 * @return (action, [src,] dst) list describing the action to be 0681 * performed. 0682 */ 0683 QStringList DirRenamer::describeAction(const RenameAction& action) const 0684 { 0685 static const char* const typeStr[] = { 0686 QT_TRANSLATE_NOOP("@default", "Create folder"), 0687 QT_TRANSLATE_NOOP("@default", "Rename folder"), 0688 QT_TRANSLATE_NOOP("@default", "Rename file"), 0689 QT_TRANSLATE_NOOP("@default", "Error") 0690 }; 0691 static constexpr unsigned numTypeStr = std::size(typeStr); 0692 0693 QStringList actionStrs; 0694 auto typeIdx = static_cast<unsigned>(action.m_type); 0695 if (typeIdx >= numTypeStr) { 0696 typeIdx = numTypeStr - 1; 0697 } 0698 actionStrs.append(QCoreApplication::translate("@default", typeStr[typeIdx])); 0699 if (!action.m_src.isEmpty()) { 0700 actionStrs.append(action.m_src); 0701 } 0702 actionStrs.append(action.m_dest); 0703 return actionStrs; 0704 } 0705 0706 /** 0707 * Check if operation is aborted. 0708 * 0709 * @return true if aborted. 0710 */ 0711 bool DirRenamer::isAborted() const 0712 { 0713 return m_aborted; 0714 } 0715 0716 /** 0717 * Clear state which is reported by isAborted(). 0718 */ 0719 void DirRenamer::clearAborted() 0720 { 0721 m_aborted = false; 0722 } 0723 0724 /** 0725 * Abort operation. 0726 */ 0727 void DirRenamer::abort() 0728 { 0729 m_aborted = true; 0730 }