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