File indexing completed on 2024-12-22 04:57:33
0001 /* 0002 SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "maildir.h" 0008 #include "keycache.h" 0009 0010 #include <QDateTime> 0011 #include <QDir> 0012 #include <QFileInfo> 0013 #include <QHostInfo> 0014 #include <QRandomGenerator> 0015 #include <QRegularExpression> 0016 #include <QUuid> 0017 0018 #include "libmaildir_debug.h" 0019 #include <Akonadi/MessageFlags> 0020 #include <KLocalizedString> 0021 #include <fcntl.h> 0022 0023 // Define it to get more debug output to expense of operating speed 0024 // #define DEBUG_KEYCACHE_CONSITENCY 0025 0026 using namespace KPIM; 0027 0028 static QRegularExpression statusSeparatorRx() 0029 { 0030 static const QRegularExpression expr(QStringLiteral(":|!")); 0031 return expr; 0032 } 0033 0034 class Maildir::Private 0035 { 0036 public: 0037 Private(const QString &p, bool isRoot) 0038 : path(p) 0039 , hostName(QHostInfo::localHostName()) 0040 , isRoot(isRoot) 0041 { 0042 // Cache object is created the first time this runs. 0043 // It will live throughout the lifetime of the application 0044 KeyCache::self()->addKeys(path); 0045 } 0046 0047 Private(const Private &rhs) 0048 : path(rhs.path) 0049 , hostName(rhs.hostName) 0050 , isRoot(rhs.isRoot) 0051 { 0052 } 0053 0054 bool operator==(const Private &rhs) const 0055 { 0056 return path == rhs.path; 0057 } 0058 0059 bool accessIsPossible(bool createMissingFolders = true); 0060 [[nodiscard]] bool canAccess(const QString &path) const; 0061 0062 [[nodiscard]] QStringList subPaths() const 0063 { 0064 QStringList paths; 0065 paths << path + QLatin1StringView("/cur"); 0066 paths << path + QLatin1StringView("/new"); 0067 paths << path + QLatin1StringView("/tmp"); 0068 return paths; 0069 } 0070 0071 [[nodiscard]] QStringList listNew() const 0072 { 0073 QDir d(path + QLatin1StringView("/new")); 0074 d.setSorting(QDir::NoSort); 0075 return d.entryList(QDir::Files); 0076 } 0077 0078 [[nodiscard]] QStringList listCurrent() const 0079 { 0080 QDir d(path + QLatin1StringView("/cur")); 0081 d.setSorting(QDir::NoSort); 0082 return d.entryList(QDir::Files); 0083 } 0084 0085 [[nodiscard]] QString findRealKey(const QString &key) const 0086 { 0087 KeyCache *keyCache = KeyCache::self(); 0088 if (keyCache->isNewKey(path, key)) { 0089 #ifdef DEBUG_KEYCACHE_CONSITENCY 0090 if (!QFile::exists(path + QString::fromLatin1("/new/") + key)) { 0091 qCDebug(LIBMAILDIR_LOG) << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/new/") + key; 0092 } 0093 #endif 0094 return path + QLatin1StringView("/new/") + key; 0095 } 0096 if (keyCache->isCurKey(path, key)) { 0097 #ifdef DEBUG_KEYCACHE_CONSITENCY 0098 if (!QFile::exists(path + QString::fromLatin1("/cur/") + key)) { 0099 qCDebug(LIBMAILDIR_LOG) << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/cur/") + key; 0100 } 0101 #endif 0102 return path + QLatin1StringView("/cur/") + key; 0103 } 0104 QString realKey = path + QLatin1StringView("/new/") + key; 0105 0106 if (QFileInfo::exists(realKey)) { 0107 keyCache->addNewKey(path, key); 0108 } else { // not in "new", search in "cur" 0109 realKey = path + QLatin1StringView("/cur/") + key; 0110 if (QFileInfo::exists(realKey)) { 0111 keyCache->addCurKey(path, key); 0112 } else { 0113 realKey.clear(); // not in "cur" either 0114 } 0115 } 0116 0117 return realKey; 0118 } 0119 0120 static QString subDirNameForFolderName(const QString &folderName) 0121 { 0122 return QStringLiteral(".%1.directory").arg(folderName); 0123 } 0124 0125 [[nodiscard]] QString subDirPath() const 0126 { 0127 QDir dir(path); 0128 return subDirNameForFolderName(dir.dirName()); 0129 } 0130 0131 bool moveAndRename(QDir &dest, const QString &newName) 0132 { 0133 if (!dest.exists()) { 0134 qCDebug(LIBMAILDIR_LOG) << "Destination does not exist"; 0135 return false; 0136 } 0137 if (dest.exists(newName) || dest.exists(subDirNameForFolderName(newName))) { 0138 qCDebug(LIBMAILDIR_LOG) << "New name already in use"; 0139 return false; 0140 } 0141 0142 if (!dest.rename(path, newName)) { 0143 qCDebug(LIBMAILDIR_LOG) << "Failed to rename maildir"; 0144 return false; 0145 } 0146 const QDir subDirs(Maildir::subDirPathForFolderPath(path)); 0147 if (subDirs.exists() && !dest.rename(subDirs.path(), subDirNameForFolderName(newName))) { 0148 qCDebug(LIBMAILDIR_LOG) << "Failed to rename subfolders"; 0149 return false; 0150 } 0151 0152 path = dest.path() + QLatin1Char('/') + newName; 0153 return true; 0154 } 0155 0156 QString path; 0157 QString hostName; 0158 QString lastError; 0159 bool isRoot; 0160 }; 0161 0162 Maildir::Maildir(const QString &path, bool isRoot) 0163 : d(new Private(path, isRoot)) 0164 { 0165 } 0166 0167 void Maildir::swap(const Maildir &rhs) 0168 { 0169 Private *p = d; 0170 d = new Private(*rhs.d); 0171 delete p; 0172 } 0173 0174 Maildir::Maildir(const Maildir &rhs) 0175 : d(new Private(*rhs.d)) 0176 { 0177 } 0178 0179 Maildir &Maildir::operator=(const Maildir &rhs) 0180 { 0181 // copy and swap, exception safe, and handles assignment to self 0182 Maildir temp(rhs); 0183 swap(temp); 0184 return *this; 0185 } 0186 0187 bool Maildir::operator==(const Maildir &rhs) const 0188 { 0189 return *d == *rhs.d; 0190 } 0191 0192 Maildir::~Maildir() 0193 { 0194 delete d; 0195 } 0196 0197 bool Maildir::Private::canAccess(const QString &path) const 0198 { 0199 // return access( QFile::encodeName( path ), R_OK | W_OK | X_OK ) != 0; 0200 // FIXME X_OK? 0201 QFileInfo d(path); 0202 return d.isReadable() && d.isWritable(); 0203 } 0204 0205 bool Maildir::Private::accessIsPossible(bool createMissingFolders) 0206 { 0207 QStringList paths = subPaths(); 0208 0209 paths.prepend(path); 0210 0211 for (const QString &p : std::as_const(paths)) { 0212 if (!QFileInfo::exists(p)) { 0213 if (!createMissingFolders) { 0214 lastError = i18n("Error opening %1; this folder is missing.", p); 0215 return false; 0216 } 0217 QDir().mkpath(p); 0218 if (!QFileInfo::exists(p)) { 0219 lastError = i18n("Error opening %1; this folder is missing.", p); 0220 return false; 0221 } 0222 } 0223 if (!canAccess(p)) { 0224 lastError = i18n( 0225 "Error opening %1; either this is not a valid " 0226 "maildir folder, or you do not have sufficient access permissions.", 0227 p); 0228 return false; 0229 } 0230 } 0231 return true; 0232 } 0233 0234 bool Maildir::isValid(bool createMissingFolders) const 0235 { 0236 if (path().isEmpty()) { 0237 return false; 0238 } 0239 if (!d->isRoot) { 0240 if (d->accessIsPossible(createMissingFolders)) { 0241 return true; 0242 } 0243 } else { 0244 const QStringList lstMaildir = subFolderList(); 0245 for (const QString &sf : lstMaildir) { 0246 const Maildir subMd = Maildir(path() + QLatin1Char('/') + sf); 0247 if (!subMd.isValid(createMissingFolders)) { 0248 d->lastError = subMd.lastError(); 0249 return false; 0250 } 0251 } 0252 return true; 0253 } 0254 return false; 0255 } 0256 0257 bool Maildir::isRoot() const 0258 { 0259 return d->isRoot; 0260 } 0261 0262 bool Maildir::create() 0263 { 0264 // FIXME: in a failure case, this will leave partially created dirs around 0265 // we should clean them up, but only if they didn't previously existed... 0266 const QStringList lstPath = d->subPaths(); 0267 for (const QString &p : lstPath) { 0268 QDir dir(p); 0269 if (!dir.exists(p)) { 0270 if (!dir.mkpath(p)) { 0271 return false; 0272 } 0273 } 0274 } 0275 return true; 0276 } 0277 0278 QString Maildir::path() const 0279 { 0280 return d->path; 0281 } 0282 0283 QString Maildir::name() const 0284 { 0285 const QDir dir(d->path); 0286 return dir.dirName(); 0287 } 0288 0289 QString Maildir::addSubFolder(const QString &path) 0290 { 0291 if (!isValid()) { 0292 return {}; 0293 } 0294 0295 // make the subdir dir 0296 QDir dir(d->path); 0297 if (!d->isRoot) { 0298 dir.cdUp(); 0299 if (!dir.exists(d->subDirPath())) { 0300 dir.mkdir(d->subDirPath()); 0301 } 0302 dir.cd(d->subDirPath()); 0303 } 0304 0305 const QString fullPath = dir.path() + QLatin1Char('/') + path; 0306 Maildir subdir(fullPath); 0307 if (subdir.create()) { 0308 return fullPath; 0309 } 0310 return {}; 0311 } 0312 0313 bool Maildir::removeSubFolder(const QString &folderName) 0314 { 0315 if (!isValid()) { 0316 return false; 0317 } 0318 QDir dir(d->path); 0319 if (!d->isRoot) { 0320 dir.cdUp(); 0321 if (!dir.exists(d->subDirPath())) { 0322 return false; 0323 } 0324 dir.cd(d->subDirPath()); 0325 } 0326 if (!dir.exists(folderName)) { 0327 return false; 0328 } 0329 0330 // remove it recursively 0331 bool result = QDir(dir.absolutePath() + QLatin1Char('/') + folderName).removeRecursively(); 0332 QString subfolderName = subDirNameForFolderName(folderName); 0333 if (dir.exists(subfolderName)) { 0334 result &= QDir(dir.absolutePath() + QLatin1Char('/') + subfolderName).removeRecursively(); 0335 } 0336 return result; 0337 } 0338 0339 Maildir Maildir::subFolder(const QString &subFolder) const 0340 { 0341 // make the subdir dir 0342 QDir dir(d->path); 0343 if (!d->isRoot) { 0344 dir.cdUp(); 0345 if (dir.exists(d->subDirPath())) { 0346 dir.cd(d->subDirPath()); 0347 } 0348 } 0349 return Maildir(dir.path() + QLatin1Char('/') + subFolder); 0350 } 0351 0352 Maildir Maildir::parent() const 0353 { 0354 if (!isValid() || d->isRoot) { 0355 return Maildir(); 0356 } 0357 QDir dir(d->path); 0358 dir.cdUp(); 0359 if (!dir.dirName().startsWith(QLatin1Char('.')) || !dir.dirName().endsWith(QLatin1StringView(".directory"))) { 0360 return Maildir(); 0361 } 0362 const QString parentName = dir.dirName().mid(1, dir.dirName().size() - 11); 0363 dir.cdUp(); 0364 dir.cd(parentName); 0365 return Maildir(dir.path()); 0366 } 0367 0368 QStringList Maildir::entryList() const 0369 { 0370 QStringList result; 0371 if (isValid()) { 0372 result = d->listNew() + d->listCurrent(); 0373 } 0374 // qCDebug(LIBMAILDIR_LOG) <<"Maildir::entryList()" << result; 0375 return result; 0376 } 0377 0378 QStringList Maildir::listCurrent() const 0379 { 0380 QStringList result; 0381 if (isValid()) { 0382 result = d->listCurrent(); 0383 } 0384 return result; 0385 } 0386 0387 QString Maildir::findRealKey(const QString &key) const 0388 { 0389 return d->findRealKey(key); 0390 } 0391 0392 QStringList Maildir::listNew() const 0393 { 0394 QStringList result; 0395 if (isValid()) { 0396 result = d->listNew(); 0397 } 0398 return result; 0399 } 0400 0401 QString Maildir::pathToNew() const 0402 { 0403 if (isValid()) { 0404 return d->path + QLatin1StringView("/new"); 0405 } 0406 return {}; 0407 } 0408 0409 QString Maildir::pathToCurrent() const 0410 { 0411 if (isValid()) { 0412 return d->path + QLatin1StringView("/cur"); 0413 } 0414 return {}; 0415 } 0416 0417 QString Maildir::subDirPath() const 0418 { 0419 QDir dir(d->path); 0420 dir.cdUp(); 0421 return dir.path() + QLatin1Char('/') + d->subDirPath(); 0422 } 0423 0424 QStringList Maildir::subFolderList() const 0425 { 0426 QDir dir(d->path); 0427 0428 // the root maildir has its subfolders directly beneath it 0429 if (!d->isRoot) { 0430 dir.cdUp(); 0431 if (!dir.exists(d->subDirPath())) { 0432 return {}; 0433 } 0434 dir.cd(d->subDirPath()); 0435 } 0436 dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); 0437 QStringList entries = dir.entryList(); 0438 entries.removeAll(QStringLiteral("cur")); 0439 entries.removeAll(QStringLiteral("new")); 0440 entries.removeAll(QStringLiteral("tmp")); 0441 return entries; 0442 } 0443 0444 QByteArray Maildir::readEntry(const QString &key) const 0445 { 0446 QByteArray result; 0447 0448 QString realKey(d->findRealKey(key)); 0449 if (realKey.isEmpty()) { 0450 // FIXME error handling? 0451 qCWarning(LIBMAILDIR_LOG) << "Maildir::readEntry unable to find: " << key; 0452 d->lastError = i18n("Cannot locate mail file %1.", key); 0453 return result; 0454 } 0455 0456 QFile f(realKey); 0457 if (!f.open(QIODevice::ReadOnly)) { 0458 d->lastError = i18n("Cannot open mail file %1.", realKey); 0459 return result; 0460 } 0461 0462 // FIXME be safer than this 0463 result = f.readAll(); 0464 0465 return result; 0466 } 0467 0468 qint64 Maildir::size(const QString &key) const 0469 { 0470 QString realKey(d->findRealKey(key)); 0471 if (realKey.isEmpty()) { 0472 // FIXME error handling? 0473 qCWarning(LIBMAILDIR_LOG) << "Maildir::size unable to find: " << key; 0474 d->lastError = i18n("Cannot locate mail file %1.", key); 0475 return -1; 0476 } 0477 0478 const QFileInfo info(realKey); 0479 if (!info.exists()) { 0480 d->lastError = i18n("Cannot open mail file %1.", realKey); 0481 return -1; 0482 } 0483 0484 return info.size(); 0485 } 0486 0487 QDateTime Maildir::lastModified(const QString &key) const 0488 { 0489 const QString realKey(d->findRealKey(key)); 0490 if (realKey.isEmpty()) { 0491 qCWarning(LIBMAILDIR_LOG) << "Maildir::lastModified unable to find: " << key; 0492 d->lastError = i18n("Cannot locate mail file %1.", key); 0493 return {}; 0494 } 0495 0496 const QFileInfo info(realKey); 0497 if (!info.exists()) { 0498 return {}; 0499 } 0500 0501 return info.lastModified(); 0502 } 0503 0504 QByteArray Maildir::readEntryHeadersFromFile(const QString &file) const 0505 { 0506 QByteArray result; 0507 0508 QFile f(file); 0509 if (!f.open(QIODevice::ReadOnly)) { 0510 // FIXME error handling? 0511 qCWarning(LIBMAILDIR_LOG) << "Maildir::readEntryHeaders unable to find: " << file; 0512 d->lastError = i18n("Cannot locate mail file %1.", file); 0513 return result; 0514 } 0515 f.map(0, qMin((qint64)8000, f.size())); 0516 for (;;) { 0517 QByteArray line = f.readLine(); 0518 if (line.isEmpty() || line.startsWith('\n')) { 0519 break; 0520 } 0521 result.append(line); 0522 } 0523 return result; 0524 } 0525 0526 QByteArray Maildir::readEntryHeaders(const QString &key) const 0527 { 0528 const QString realKey(d->findRealKey(key)); 0529 if (realKey.isEmpty()) { 0530 qCWarning(LIBMAILDIR_LOG) << "Maildir::readEntryHeaders unable to find: " << key; 0531 d->lastError = i18n("Cannot locate mail file %1.", key); 0532 return {}; 0533 } 0534 0535 return readEntryHeadersFromFile(realKey); 0536 } 0537 0538 static QString createUniqueFileName() 0539 { 0540 const qint64 time = QDateTime::currentMSecsSinceEpoch(); 0541 const int r = QRandomGenerator::global()->bounded(1000); 0542 const QString identifier = QLatin1StringView("R") + QString::number(r); 0543 0544 QString fileName = QString::number(time) + QLatin1Char('.') + identifier + QLatin1Char('.'); 0545 0546 return fileName; 0547 } 0548 0549 bool Maildir::writeEntry(const QString &key, const QByteArray &data) 0550 { 0551 QString realKey(d->findRealKey(key)); 0552 if (realKey.isEmpty()) { 0553 // FIXME error handling? 0554 qCWarning(LIBMAILDIR_LOG) << "Maildir::writeEntry unable to find: " << key; 0555 d->lastError = i18n("Cannot locate mail file %1.", key); 0556 return false; 0557 } 0558 QFile f(realKey); 0559 bool result = f.open(QIODevice::WriteOnly); 0560 result = result & (f.write(data) != -1); 0561 f.close(); 0562 if (!result) { 0563 d->lastError = i18n("Cannot write to mail file %1.", realKey); 0564 return false; 0565 } 0566 return true; 0567 } 0568 0569 QString Maildir::addEntry(const QByteArray &data) 0570 { 0571 QString uniqueKey; 0572 QString key; 0573 QString finalKey; 0574 QString curKey; 0575 0576 // QUuid doesn't return globally unique identifiers, therefore we query until we 0577 // get one that doesn't exists yet 0578 do { 0579 uniqueKey = createUniqueFileName() + d->hostName; 0580 key = d->path + QLatin1StringView("/tmp/") + uniqueKey; 0581 finalKey = d->path + QLatin1StringView("/new/") + uniqueKey; 0582 curKey = d->path + QLatin1StringView("/cur/") + uniqueKey; 0583 } while (QFile::exists(key) || QFile::exists(finalKey) || QFile::exists(curKey)); 0584 0585 QFile f(key); 0586 bool result = f.open(QIODevice::WriteOnly); 0587 result = result & (f.write(data) != -1); 0588 f.close(); 0589 if (!result) { 0590 d->lastError = i18n("Cannot write to mail file %1.", key); 0591 return {}; 0592 } 0593 /* 0594 * FIXME: 0595 * 0596 * The whole point of the locking free maildir idea is that the moves between 0597 * the internal directories are atomic. Afaik QFile::rename does not guarantee 0598 * that, so this will need to be done properly. - ta 0599 * 0600 * For reference: http://trolltech.com/developer/task-tracker/index_html?method=entry&id=211215 0601 */ 0602 if (!f.rename(finalKey)) { 0603 qCWarning(LIBMAILDIR_LOG) << "Maildir: Failed to add entry: " << finalKey << "! Error: " << f.errorString(); 0604 d->lastError = i18n("Failed to create mail file %1. The error was: %2", finalKey, f.errorString()); 0605 return {}; 0606 } 0607 KeyCache *keyCache = KeyCache::self(); 0608 keyCache->removeKey(d->path, key); // remove all keys, be it "cur" or "new" first 0609 keyCache->addNewKey(d->path, key); // and add a key for "new", as the mail was moved there 0610 return uniqueKey; 0611 } 0612 0613 bool Maildir::removeEntry(const QString &key) 0614 { 0615 QString realKey(d->findRealKey(key)); 0616 if (realKey.isEmpty()) { 0617 qCWarning(LIBMAILDIR_LOG) << "Maildir::removeEntry unable to find: " << key; 0618 return false; 0619 } 0620 KeyCache *keyCache = KeyCache::self(); 0621 keyCache->removeKey(d->path, key); 0622 return QFile::remove(realKey); 0623 } 0624 0625 QString Maildir::changeEntryFlags(const QString &key, const Akonadi::Item::Flags &flags) 0626 { 0627 QString realKey(d->findRealKey(key)); 0628 if (realKey.isEmpty()) { 0629 qCWarning(LIBMAILDIR_LOG) << "Maildir::changeEntryFlags unable to find: " << key; 0630 d->lastError = i18n("Cannot locate mail file %1.", key); 0631 return {}; 0632 } 0633 0634 const QRegularExpression rx = statusSeparatorRx(); 0635 QString finalKey = key.left(key.indexOf(rx)); 0636 0637 QStringList mailDirFlags; 0638 for (const Akonadi::Item::Flag &flag : flags) { 0639 if (flag == Akonadi::MessageFlags::Forwarded) { 0640 mailDirFlags << QStringLiteral("P"); 0641 } else if (flag == Akonadi::MessageFlags::Replied) { 0642 mailDirFlags << QStringLiteral("R"); 0643 } else if (flag == Akonadi::MessageFlags::Seen) { 0644 mailDirFlags << QStringLiteral("S"); 0645 } else if (flag == Akonadi::MessageFlags::Deleted) { 0646 mailDirFlags << QStringLiteral("T"); 0647 } else if (flag == Akonadi::MessageFlags::Flagged) { 0648 mailDirFlags << QStringLiteral("F"); 0649 } 0650 } 0651 mailDirFlags.sort(); 0652 if (!mailDirFlags.isEmpty()) { 0653 #ifdef Q_OS_WIN 0654 finalKey.append(QLatin1StringView("!2,") + mailDirFlags.join(QString())); 0655 #else 0656 finalKey.append(QLatin1StringView(":2,") + mailDirFlags.join(QString())); 0657 #endif 0658 } 0659 0660 const QString newUniqueKey = finalKey; // key without path 0661 finalKey.prepend(d->path + QLatin1StringView("/cur/")); 0662 0663 if (realKey == finalKey) { 0664 // Somehow it already is named this way (e.g. migration bug -> wrong status in akonadi) 0665 return newUniqueKey; 0666 } 0667 0668 QFile f(realKey); 0669 if (QFile::exists(finalKey)) { 0670 QFile destFile(finalKey); 0671 QByteArray destContent; 0672 if (destFile.open(QIODevice::ReadOnly)) { 0673 destContent = destFile.readAll(); 0674 destFile.close(); 0675 } 0676 QByteArray sourceContent; 0677 if (f.open(QIODevice::ReadOnly)) { 0678 sourceContent = f.readAll(); 0679 f.close(); 0680 } 0681 0682 if (destContent != sourceContent) { 0683 QString newFinalKey = QLatin1StringView("1-") + newUniqueKey; 0684 int i = 1; 0685 const QString currentPath = d->path + QLatin1StringView("/cur/"); 0686 while (QFile::exists(currentPath + newFinalKey)) { 0687 i++; 0688 newFinalKey = QString::number(i) + QLatin1Char('-') + newUniqueKey; 0689 } 0690 finalKey = currentPath + newFinalKey; 0691 } else { 0692 QFile::remove(finalKey); // they are the same 0693 } 0694 } 0695 0696 if (!f.rename(finalKey)) { 0697 qCWarning(LIBMAILDIR_LOG) << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString(); 0698 d->lastError = i18n("Failed to update the file name %1 to %2 on the disk. The error was: %3.", f.fileName(), finalKey, f.errorString()); 0699 return {}; 0700 } 0701 0702 KeyCache *keyCache = KeyCache::self(); 0703 keyCache->removeKey(d->path, key); 0704 keyCache->addCurKey(d->path, newUniqueKey); 0705 0706 return newUniqueKey; 0707 } 0708 0709 Akonadi::Item::Flags Maildir::readEntryFlags(const QString &key) const 0710 { 0711 Akonadi::Item::Flags flags; 0712 0713 const QRegularExpression rx = statusSeparatorRx(); 0714 const int index = key.indexOf(rx); 0715 if (index != -1) { 0716 const QStringView mailDirFlags = QStringView(key).mid(index + 3); // after "(:|!)2," 0717 const int flagSize(mailDirFlags.size()); 0718 for (int i = 0; i < flagSize; ++i) { 0719 const QChar flag = mailDirFlags.at(i); 0720 if (flag == QLatin1Char('P')) { 0721 flags << Akonadi::MessageFlags::Forwarded; 0722 } else if (flag == QLatin1Char('R')) { 0723 flags << Akonadi::MessageFlags::Replied; 0724 } else if (flag == QLatin1Char('S')) { 0725 flags << Akonadi::MessageFlags::Seen; 0726 } else if (flag == QLatin1Char('F')) { 0727 flags << Akonadi::MessageFlags::Flagged; 0728 } 0729 } 0730 } 0731 0732 return flags; 0733 } 0734 0735 bool Maildir::moveTo(const Maildir &newParent) 0736 { 0737 if (d->isRoot) { 0738 return false; // not supported 0739 } 0740 0741 QDir newDir(newParent.path()); 0742 if (!newParent.d->isRoot) { 0743 newDir.cdUp(); 0744 if (!newDir.exists(newParent.d->subDirPath())) { 0745 newDir.mkdir(newParent.d->subDirPath()); 0746 } 0747 newDir.cd(newParent.d->subDirPath()); 0748 } 0749 0750 QDir currentDir(d->path); 0751 currentDir.cdUp(); 0752 0753 if (newDir == currentDir) { 0754 return true; 0755 } 0756 0757 return d->moveAndRename(newDir, name()); 0758 } 0759 0760 bool Maildir::rename(const QString &newName) 0761 { 0762 if (name() == newName) { 0763 return true; 0764 } 0765 if (d->isRoot) { 0766 return false; // not (yet) supported 0767 } 0768 0769 QDir dir(d->path); 0770 dir.cdUp(); 0771 0772 return d->moveAndRename(dir, newName); 0773 } 0774 0775 QString Maildir::moveEntryTo(const QString &key, const Maildir &destination) 0776 { 0777 const QString realKey(d->findRealKey(key)); 0778 if (realKey.isEmpty()) { 0779 qCWarning(LIBMAILDIR_LOG) << "Unable to find: " << key; 0780 d->lastError = i18n("Cannot locate mail file %1.", key); 0781 return {}; 0782 } 0783 QFile f(realKey); 0784 // ### is this safe regarding the maildir locking scheme? 0785 const QString targetKey = destination.path() + QStringLiteral("/new/") + key; 0786 if (!f.rename(targetKey)) { 0787 qCDebug(LIBMAILDIR_LOG) << "Failed to rename" << realKey << "to" << targetKey << "! Error: " << f.errorString(); 0788 d->lastError = f.errorString(); 0789 return {}; 0790 } 0791 0792 KeyCache *keyCache = KeyCache::self(); 0793 0794 keyCache->addNewKey(destination.path(), key); 0795 keyCache->removeKey(d->path, key); 0796 0797 return key; 0798 } 0799 0800 QString Maildir::subDirPathForFolderPath(const QString &folderPath) 0801 { 0802 QDir dir(folderPath); 0803 const QString dirName = dir.dirName(); 0804 dir.cdUp(); 0805 return QFileInfo(dir, Private::subDirNameForFolderName(dirName)).filePath(); 0806 } 0807 0808 QString Maildir::subDirNameForFolderName(const QString &folderName) 0809 { 0810 return Private::subDirNameForFolderName(folderName); 0811 } 0812 0813 void Maildir::removeCachedKeys(const QStringList &keys) 0814 { 0815 KeyCache *keyCache = KeyCache::self(); 0816 for (const QString &key : keys) { 0817 keyCache->removeKey(d->path, key); 0818 } 0819 } 0820 0821 void Maildir::refreshKeyCache() 0822 { 0823 KeyCache::self()->refreshKeys(d->path); 0824 } 0825 0826 QString Maildir::lastError() const 0827 { 0828 return d->lastError; 0829 }