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