From 1710fab0965d32b883dfcc327c36d3fd38a91357 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Tue, 15 Dec 2015 17:05:20 +0100 Subject: A read-only maildir resource. Respectively a first prototype thereof. --- examples/maildirresource/libmaildir/CMakeLists.txt | 8 + examples/maildirresource/libmaildir/keycache.cpp | 88 +++ examples/maildirresource/libmaildir/keycache.h | 77 ++ examples/maildirresource/libmaildir/maildir.cpp | 865 +++++++++++++++++++++ examples/maildirresource/libmaildir/maildir.h | 266 +++++++ 5 files changed, 1304 insertions(+) create mode 100644 examples/maildirresource/libmaildir/CMakeLists.txt create mode 100644 examples/maildirresource/libmaildir/keycache.cpp create mode 100644 examples/maildirresource/libmaildir/keycache.h create mode 100644 examples/maildirresource/libmaildir/maildir.cpp create mode 100644 examples/maildirresource/libmaildir/maildir.h (limited to 'examples/maildirresource/libmaildir') diff --git a/examples/maildirresource/libmaildir/CMakeLists.txt b/examples/maildirresource/libmaildir/CMakeLists.txt new file mode 100644 index 0000000..e7803f5 --- /dev/null +++ b/examples/maildirresource/libmaildir/CMakeLists.txt @@ -0,0 +1,8 @@ +# add_subdirectory( tests ) + +set(maildir_LIB_SRCS keycache.cpp maildir.cpp) + +add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS}) +qt5_use_modules(maildir Core Network) +# set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) +install(TARGETS maildir ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/examples/maildirresource/libmaildir/keycache.cpp b/examples/maildirresource/libmaildir/keycache.cpp new file mode 100644 index 0000000..814ce6c --- /dev/null +++ b/examples/maildirresource/libmaildir/keycache.cpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2012 Andras Mantia + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "keycache.h" + +#include + +KeyCache* KeyCache::mSelf = 0; + +void KeyCache::addKeys( const QString& dir ) +{ + if ( !mNewKeys.contains( dir ) ) { + mNewKeys.insert( dir, listNew( dir ) ); + //kDebug() << "Added new keys for: " << dir; + } + + if ( !mCurKeys.contains( dir ) ) { + mCurKeys.insert( dir, listCurrent( dir ) ); + //kDebug() << "Added cur keys for: " << dir; + } +} + +void KeyCache::refreshKeys( const QString& dir ) +{ + mNewKeys.remove( dir ); + mCurKeys.remove( dir ); + addKeys( dir ); +} + +void KeyCache::addNewKey( const QString& dir, const QString& key ) +{ + mNewKeys[dir].insert( key ); + // kDebug() << "Added new key for : " << dir << " key: " << key; +} + +void KeyCache::addCurKey( const QString& dir, const QString& key ) +{ + mCurKeys[dir].insert( key ); + // kDebug() << "Added cur key for : " << dir << " key:" << key; +} + +void KeyCache::removeKey( const QString& dir, const QString& key ) +{ + //kDebug() << "Removed new and cur key for: " << dir << " key:" << key; + mNewKeys[dir].remove( key ); + mCurKeys[dir].remove( key ); +} + +bool KeyCache::isCurKey( const QString& dir, const QString& key ) const +{ + return mCurKeys.value( dir ).contains( key ); +} + +bool KeyCache::isNewKey( const QString& dir, const QString& key ) const +{ + return mNewKeys.value( dir ).contains( key ); +} + +QSet< QString > KeyCache::listNew( const QString& dir ) const +{ + QDir d( dir + QString::fromLatin1( "/new" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ).toSet(); +} + +QSet< QString > KeyCache::listCurrent( const QString& dir ) const +{ + QDir d( dir + QString::fromLatin1( "/cur" ) ); + d.setSorting(QDir::NoSort); + return d.entryList( QDir::Files ).toSet(); +} + diff --git a/examples/maildirresource/libmaildir/keycache.h b/examples/maildirresource/libmaildir/keycache.h new file mode 100644 index 0000000..3cce6f0 --- /dev/null +++ b/examples/maildirresource/libmaildir/keycache.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2012 Andras Mantia + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef KEYCACHE_H +#define KEYCACHE_H + +/** @brief a cache for the maildir keys (file names in cur/new folders). + * It is used to find if a file is in cur or new + */ + +#include +#include + +class KeyCache { + +public: + static KeyCache *self() + { + if ( !mSelf ) + mSelf = new KeyCache(); + return mSelf; + } + + /** Find the new and cur keys on the disk for @param dir and add them to the cache */ + void addKeys( const QString& dir ); + + /** Refresh the new and cur keys for @param dir */ + void refreshKeys( const QString& dir ); + + /** Add a "new" key for @param dir. */ + void addNewKey( const QString& dir, const QString& key ); + + /** Add a "cur" key for @param dir. */ + void addCurKey( const QString& dir, const QString& key ); + + /** Remove all keys associated with @param dir. */ + void removeKey( const QString& dir, const QString& key ); + + /** Check if the @param key is a "cur" key in @param dir */ + bool isCurKey( const QString& dir, const QString& key ) const; + + /** Check if the @param key is a "new" key in @param dir */ + bool isNewKey( const QString& dir, const QString& key ) const; + +private: + KeyCache() { + } + + QSet listNew( const QString& dir ) const; + + QSet listCurrent( const QString& dir ) const; + + QHash< QString, QSet > mNewKeys; + QHash< QString, QSet > mCurKeys; + + static KeyCache* mSelf; + +}; + + +#endif // KEYCACHE_H diff --git a/examples/maildirresource/libmaildir/maildir.cpp b/examples/maildirresource/libmaildir/maildir.cpp new file mode 100644 index 0000000..37bf6ea --- /dev/null +++ b/examples/maildirresource/libmaildir/maildir.cpp @@ -0,0 +1,865 @@ +/* + Copyright (c) 2007 Till Adam + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "maildir.h" +#include "keycache.h" + +#include +#include +#include +#include +#include + +#include +#include + +//Define it to get more debug output to expense of operating speed +// #define DEBUG_KEYCACHE_CONSITENCY + + +// static void initRandomSeed() +// { +// static bool init = false; +// if (!init) { +// unsigned int seed; +// init = true; +// int fd = KDE_open("/dev/urandom", O_RDONLY); +// if (fd < 0 || ::read(fd, &seed, sizeof(seed)) != sizeof(seed)) { +// // No /dev/urandom... try something else. +// srand(getpid()); +// seed = rand() + time(0); +// } +// +// if (fd >= 0) +// close(fd); +// +// qsrand(seed); +// } +// } + + +bool removeDirAndContentsRecursively(const QString & path) +{ + bool success = true; + + QDir d; + d.setPath(path); + d.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks); + + QFileInfoList list = d.entryInfoList(); + + Q_FOREACH (const QFileInfo &fi, list) { + if (fi.isDir()) { + if (fi.fileName() != QLatin1String(".") && fi.fileName() != QLatin1String("..")) { + success = success && removeDirAndContentsRecursively(fi.absoluteFilePath()); + } + } else { + success = success && d.remove(fi.absoluteFilePath()); + } + } + + if (success) { + success = success && d.rmdir(path); // nuke ourselves, we should be empty now + } + return success; +} + +using namespace KPIM; + +Q_GLOBAL_STATIC_WITH_ARGS(QRegExp, statusSeparatorRx, (":|!")) + +class Maildir::Private +{ +public: + Private(const QString& p, bool isRoot) + :path(p), isRoot(isRoot) + { + hostName = QHostInfo::localHostName(); + // The default implementation of QUuid::createUuid() doesn't use + // a seed that is random enough. Therefor we use our own initialization + // until this issue will be fixed in Qt 4.7. + // initRandomSeed(); + + //Cache object is created the first time this runs. + //It will live throughout the lifetime of the application + KeyCache::self()->addKeys(path); + } + + Private(const Private& rhs) + { + path = rhs.path; + isRoot = rhs.isRoot; + hostName = rhs.hostName; + } + + bool operator==(const Private& rhs) const + { + return path == rhs.path; + } + bool accessIsPossible(bool createMissingFolders = true); + bool canAccess(const QString& path) const; + + QStringList subPaths() const + { + QStringList paths; + paths << path + QString::fromLatin1("/cur"); + paths << path + QString::fromLatin1("/new"); + paths << path + QString::fromLatin1("/tmp"); + return paths; + } + + QStringList listNew() const + { + QDir d(path + QString::fromLatin1("/new")); + d.setSorting(QDir::NoSort); + return d.entryList(QDir::Files); + } + + QStringList listCurrent() const + { + QDir d(path + QString::fromLatin1("/cur")); + d.setSorting(QDir::NoSort); + return d.entryList(QDir::Files); + } + + QString findRealKey(const QString& key) const + { + KeyCache* keyCache = KeyCache::self(); + if (keyCache->isNewKey(path, key)) { +#ifdef DEBUG_KEYCACHE_CONSITENCY + if (!QFile::exists(path + QString::fromLatin1("/new/") + key)) { + qDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/new/") + key; + } +#endif + return path + QString::fromLatin1("/new/") + key; + } + if (keyCache->isCurKey(path, key)) { +#ifdef DEBUG_KEYCACHE_CONSITENCY + if (!QFile::exists(path + QString::fromLatin1("/cur/") + key)) { + qDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/cur/") + key; + } +#endif + return path + QString::fromLatin1("/cur/") + key; + } + QString realKey = path + QString::fromLatin1("/new/") + key; + + QFile f(realKey); + if (f.exists()) { + keyCache->addNewKey(path, key); + } else { //not in "new", search in "cur" + realKey = path + QString::fromLatin1("/cur/") + key; + QFile f2(realKey); + if (f2.exists()) { + keyCache->addCurKey(path, key); + } else { + realKey.clear(); //not in "cur" either + } + } + + return realKey; + } + + static QString subDirNameForFolderName(const QString &folderName) + { + return QString::fromLatin1(".%1.directory").arg(folderName); + } + + QString subDirPath() const + { + QDir dir(path); + return subDirNameForFolderName(dir.dirName()); + } + + bool moveAndRename(QDir &dest, const QString &newName) + { + if (!dest.exists()) { + qDebug() << "Destination does not exist"; + return false; + } + if (dest.exists(newName) || dest.exists(subDirNameForFolderName(newName))) { + qDebug() << "New name already in use"; + return false; + } + + if (!dest.rename(path, newName)) { + qDebug() << "Failed to rename maildir"; + return false; + } + const QDir subDirs(Maildir::subDirPathForFolderPath(path)); + if (subDirs.exists() && !dest.rename(subDirs.path(), subDirNameForFolderName(newName))) { + qDebug() << "Failed to rename subfolders"; + return false; + } + + path = dest.path() + QDir::separator() + newName; + return true; + } + + QString path; + bool isRoot; + QString hostName; + QString lastError; +}; + +Maildir::Maildir(const QString& path, bool isRoot) +:d(new Private(path, isRoot)) +{ +} + +void Maildir::swap(const Maildir &rhs) +{ + Private *p = d; + d = new Private(*rhs.d); + delete p; +} + + +Maildir::Maildir(const Maildir & rhs) + :d(new Private(*rhs.d)) + +{ +} + +Maildir& Maildir::operator= (const Maildir & rhs) +{ + // copy and swap, exception safe, and handles assignment to self + Maildir temp(rhs); + swap(temp); + return *this; +} + + +bool Maildir::operator== (const Maildir & rhs) const +{ + return *d == *rhs.d; +} + + +Maildir::~Maildir() +{ + delete d; +} + +bool Maildir::Private::canAccess(const QString& path) const +{ + //return access(QFile::encodeName(path), R_OK | W_OK | X_OK) != 0; + // FIXME X_OK? + QFileInfo d(path); + return d.isReadable() && d.isWritable(); +} + +bool Maildir::Private::accessIsPossible(bool createMissingFolders) +{ + QStringList paths = subPaths(); + + paths.prepend(path); + + Q_FOREACH (const QString &p, paths) { + if (!QFile::exists(p)) { + if (!createMissingFolders) { + // lastError = i18n("Error opening %1; this folder is missing.", p); + return false; + } + QDir().mkpath(p); + if (!QFile::exists(p)) { + // lastError = i18n("Error opening %1; this folder is missing.", p); + return false; + } + } + if (!canAccess(p)) { + // lastError = i18n("Error opening %1; either this is not a valid " + // "maildir folder, or you do not have sufficient access permissions." ,p); + return false; + } + } + return true; +} + +bool Maildir::isValid(bool createMissingFolders) const +{ + if (path().isEmpty()) { + return false; + } + if (!d->isRoot) { + if (d->accessIsPossible(createMissingFolders)) { + return true; + } + } else { + Q_FOREACH (const QString &sf, subFolderList()) { + const Maildir subMd = Maildir(path() + QLatin1Char('/') + sf); + if (!subMd.isValid()) { + d->lastError = subMd.lastError(); + return false; + } + } + return true; + } + return false; +} + +bool Maildir::isRoot() const +{ + return d->isRoot; +} + +bool Maildir::create() +{ + // FIXME: in a failure case, this will leave partially created dirs around + // we should clean them up, but only if they didn't previously existed... + Q_FOREACH (const QString &p, d->subPaths()) { + QDir dir(p); + if (!dir.exists(p)) { + if (!dir.mkpath(p)) + return false; + } + } + return true; +} + +QString Maildir::path() const +{ + return d->path; +} + +QString Maildir::name() const +{ + const QDir dir(d->path); + return dir.dirName(); +} + +QString Maildir::addSubFolder(const QString& path) +{ + if (!isValid()) + return QString(); + + // make the subdir dir + QDir dir(d->path); + if (!d->isRoot) { + dir.cdUp(); + if (!dir.exists(d->subDirPath())) + dir.mkdir(d->subDirPath()); + dir.cd(d->subDirPath()); + } + + const QString fullPath = dir.path() + QLatin1Char('/') + path; + Maildir subdir(fullPath); + if (subdir.create()) + return fullPath; + return QString(); +} + +bool Maildir::removeSubFolder(const QString& folderName) +{ + if (!isValid()) return false; + QDir dir(d->path); + if (!d->isRoot) { + dir.cdUp(); + if (!dir.exists(d->subDirPath())) return false; + dir.cd(d->subDirPath()); + } + if (!dir.exists(folderName)) return false; + + // remove it recursively + bool result = removeDirAndContentsRecursively(dir.absolutePath() + QLatin1Char('/') + folderName); + QString subfolderName = subDirNameForFolderName(folderName); + if (dir.exists(subfolderName)) + result &= removeDirAndContentsRecursively(dir.absolutePath() + QLatin1Char('/') + subfolderName); + return result; +} + +Maildir Maildir::subFolder(const QString& subFolder) const +{ + // make the subdir dir + QDir dir(d->path); + if (!d->isRoot) { + dir.cdUp(); + if (dir.exists(d->subDirPath())) { + dir.cd(d->subDirPath()); + } + } + return Maildir(dir.path() + QLatin1Char('/') + subFolder); +} + +Maildir Maildir::parent() const +{ + if (!isValid() || d->isRoot) + return Maildir(); + QDir dir(d->path); + dir.cdUp(); + if (!dir.dirName().startsWith(QLatin1Char('.')) || !dir.dirName().endsWith(QLatin1String(".directory"))) + return Maildir(); + const QString parentName = dir.dirName().mid(1, dir.dirName().size() - 11); + dir.cdUp(); + dir.cd(parentName); + return Maildir (dir.path()); +} + +QStringList Maildir::entryList() const +{ + QStringList result; + if (isValid()) { + result += d->listNew(); + result += d->listCurrent(); + } + // qDebug() <<"Maildir::entryList()" << result; + return result; +} + +QStringList Maildir::listCurrent() const +{ + QStringList result; + if (isValid()) { + result += d->listCurrent(); + } + return result; +} + +QString Maildir::findRealKey(const QString& key) const +{ + return d->findRealKey(key); +} + + +QStringList Maildir::listNew() const +{ + QStringList result; + if (isValid()) { + result += d->listNew(); + } + return result; +} + +QString Maildir::pathToNew() const +{ + if (isValid()) { + return d->path + QString::fromLatin1("/new"); + } + return QString(); +} + +QString Maildir::pathToCurrent() const +{ + if (isValid()) { + return d->path + QString::fromLatin1("/cur"); + } + return QString(); +} + +QString Maildir::subDirPath() const +{ + QDir dir(d->path); + dir.cdUp(); + return dir.path() + QDir::separator() + d->subDirPath(); +} + + + +QStringList Maildir::subFolderList() const +{ + QDir dir(d->path); + + // the root maildir has its subfolders directly beneath it + if (!d->isRoot) { + dir.cdUp(); + if (!dir.exists(d->subDirPath())) + return QStringList(); + dir.cd(d->subDirPath()); + } + dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + QStringList entries = dir.entryList(); + entries.removeAll(QLatin1String("cur")); + entries.removeAll(QLatin1String("new")); + entries.removeAll(QLatin1String("tmp")); + return entries; +} + +QByteArray Maildir::readEntry(const QString& key) const +{ + QByteArray result; + + QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + // FIXME error handling? + qWarning() << "Maildir::readEntry unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." ,key); + return result; + } + + QFile f(realKey); + if (!f.open(QIODevice::ReadOnly)) { + // d->lastError = i18n("Cannot open mail file %1.", realKey); + return result; + } + + // FIXME be safer than this + result = f.readAll(); + + return result; +} +qint64 Maildir::size(const QString& key) const +{ + QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + // FIXME error handling? + qWarning() << "Maildir::size unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." , key); + return -1; + } + + QFileInfo info(realKey); + if (!info.exists()) { + // d->lastError = i18n("Cannot open mail file %1." ,realKey); + return -1; + } + + return info.size(); +} + +QDateTime Maildir::lastModified(const QString& key) const +{ + const QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + qWarning() << "Maildir::lastModified unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." , key); + return QDateTime(); + } + + const QFileInfo info(realKey); + if (!info.exists()) + return QDateTime(); + + return info.lastModified(); +} + +QByteArray Maildir::readEntryHeadersFromFile(const QString& file) const +{ + QByteArray result; + + QFile f(file); + if (!f.open(QIODevice::ReadOnly)) { + // FIXME error handling? + qWarning() << "Maildir::readEntryHeaders unable to find: " << file; + // d->lastError = i18n("Cannot locate mail file %1." , file); + return result; + } + f.map(0, qMin((qint64)8000, f.size())); + forever { + QByteArray line = f.readLine(); + if (line.isEmpty() || line.startsWith('\n')) + break; + result.append(line); + } + return result; +} + +QByteArray Maildir::readEntryHeaders(const QString& key) const +{ + const QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + qWarning() << "Maildir::readEntryHeaders unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." , key); + return QByteArray(); + } + + return readEntryHeadersFromFile(realKey); +} + + +static QString createUniqueFileName() +{ + qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000; + int r = qrand() % 1000; + QString identifier = QLatin1String("R") + QString::number(r); + + QString fileName = QString::number(time) + QLatin1Char('.') + identifier + QLatin1Char('.'); + + return fileName; +} + +bool Maildir::writeEntry(const QString& key, const QByteArray& data) +{ + QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + // FIXME error handling? + qWarning() << "Maildir::writeEntry unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." ,key); + return false; + } + QFile f(realKey); + bool result = f.open(QIODevice::WriteOnly); + result = result & (f.write(data) != -1); + f.close(); + if (!result) { + // d->lastError = i18n("Cannot write to mail file %1." ,realKey); + return false; + } + return true; +} + +QString Maildir::addEntry(const QByteArray& data) +{ + QString uniqueKey; + QString key; + QString finalKey; + QString curKey; + + // QUuid doesn't return globally unique identifiers, therefor we query until we + // get one that doesn't exists yet + do { + uniqueKey = createUniqueFileName() + d->hostName; + key = d->path + QLatin1String("/tmp/") + uniqueKey; + finalKey = d->path + QLatin1String("/new/") + uniqueKey; + curKey = d->path + QLatin1String("/cur/") + uniqueKey; + } while (QFile::exists(key) || QFile::exists(finalKey) || QFile::exists(curKey)); + + QFile f(key); + bool result = f.open(QIODevice::WriteOnly); + result = result & (f.write(data) != -1); + f.close(); + if (!result) { + // d->lastError = i18n("Cannot write to mail file %1." , key); + return QString(); + } + /* + * FIXME: + * + * The whole point of the locking free maildir idea is that the moves between + * the internal directories are atomic. Afaik QFile::rename does not guarantee + * that, so this will need to be done properly. - ta + * + * For reference: http://trolltech.com/developer/task-tracker/index_html?method=entry&id=211215 + */ + if (!f.rename(finalKey)) { + qWarning() << "Maildir: Failed to add entry: " << finalKey << "! Error: " << f.errorString(); + // d->lastError = i18n("Failed to create mail file %1. The error was: %2" , finalKey, f.errorString()); + return QString(); + } + KeyCache *keyCache = KeyCache::self(); + keyCache->removeKey(d->path, key); //remove all keys, be it "cur" or "new" first + keyCache->addNewKey(d->path, key); //and add a key for "new", as the mail was moved there + return uniqueKey; +} + +bool Maildir::removeEntry(const QString& key) +{ + QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + qWarning() << "Maildir::removeEntry unable to find: " << key; + return false; + } + KeyCache *keyCache = KeyCache::self(); + keyCache->removeKey(d->path, key); + return QFile::remove(realKey); +} + +// QString Maildir::changeEntryFlags(const QString& key, const Akonadi::Item::Flags& flags) +// { +// QString realKey(d->findRealKey(key)); +// if (realKey.isEmpty()) { +// qWarning() << "Maildir::changeEntryFlags unable to find: " << key; +// d->lastError = i18n("Cannot locate mail file %1." , key); +// return QString(); +// } +// +// const QRegExp rx = *(statusSeparatorRx()); +// QString finalKey = key.left(key.indexOf(rx)); +// +// QStringList mailDirFlags; +// Q_FOREACH (const Akonadi::Item::Flag &flag, flags) { +// if (flag == Akonadi::MessageFlags::Forwarded) +// mailDirFlags << QLatin1String("P"); +// if (flag == Akonadi::MessageFlags::Replied) +// mailDirFlags << QLatin1String("R"); +// if (flag == Akonadi::MessageFlags::Seen) +// mailDirFlags << QLatin1String("S"); +// if (flag == Akonadi::MessageFlags::Deleted) +// mailDirFlags << QLatin1String("T"); +// if (flag == Akonadi::MessageFlags::Flagged) +// mailDirFlags << QLatin1String("F"); +// } +// mailDirFlags.sort(); +// if (!mailDirFlags.isEmpty()) { +// #ifdef Q_OS_WIN +// finalKey.append(QLatin1String("!2,") + mailDirFlags.join(QString())); +// #else +// finalKey.append(QLatin1String(":2,") + mailDirFlags.join(QString())); +// #endif +// } +// +// QString newUniqueKey = finalKey; //key without path +// finalKey.prepend(d->path + QString::fromLatin1("/cur/")); +// +// if (realKey == finalKey) { +// // Somehow it already is named this way (e.g. migration bug -> wrong status in akonadi) +// return newUniqueKey; +// } +// +// QFile f(realKey); +// if (QFile::exists(finalKey)) { +// QFile destFile(finalKey); +// QByteArray destContent; +// if (destFile.open(QIODevice::ReadOnly)) { +// destContent = destFile.readAll(); +// destFile.close(); +// } +// QByteArray sourceContent; +// if (f.open(QIODevice::ReadOnly)) { +// sourceContent = f.readAll(); +// f.close(); +// } +// +// if (destContent != sourceContent) { +// QString newFinalKey = QLatin1String("1-") + newUniqueKey; +// int i = 1; +// while (QFile::exists(d->path + QString::fromLatin1("/cur/") + newFinalKey)) { +// i++; +// newFinalKey = QString::number(i) + QLatin1Char('-') + newUniqueKey; +// } +// finalKey = d->path + QString::fromLatin1("/cur/") + newFinalKey; +// } else { +// QFile::remove(finalKey); //they are the same +// } +// } +// +// if (!f.rename(finalKey)) { +// qWarning() << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString(); +// d->lastError = i18n("Failed to update the file name %1 to %2 on the disk. The error was: %3." , f.fileName(), finalKey, f.errorString()); +// return QString(); +// } +// +// KeyCache *keyCache = KeyCache::self(); +// keyCache->removeKey(d->path, key); +// keyCache->addCurKey(d->path, newUniqueKey); +// +// return newUniqueKey; +// } +// +Maildir::Flags Maildir::readEntryFlags(const QString& key) const +{ + Flags flags; + const QRegExp rx = *(statusSeparatorRx()); + const int index = key.indexOf(rx); + if (index != -1) { + const QString mailDirFlags = key.mid(index + 3); // after "(:|!)2," + const int flagSize(mailDirFlags.size()); + for (int i = 0; i < flagSize; ++i) { + if (mailDirFlags[i] == QLatin1Char('P')) + flags |= Forwarded; + else if (mailDirFlags[i] == QLatin1Char('R')) + flags |= Replied; + else if (mailDirFlags[i] == QLatin1Char('S')) + flags |= Seen; + else if (mailDirFlags[i] == QLatin1Char('F')) + flags |= Flagged; + } + } + + return flags; +} + +bool Maildir::moveTo(const Maildir &newParent) +{ + if (d->isRoot) + return false; // not supported + + QDir newDir(newParent.path()); + if (!newParent.d->isRoot) { + newDir.cdUp(); + if (!newDir.exists(newParent.d->subDirPath())) + newDir.mkdir(newParent.d->subDirPath()); + newDir.cd(newParent.d->subDirPath()); + } + + QDir currentDir(d->path); + currentDir.cdUp(); + + if (newDir == currentDir) + return true; + + return d->moveAndRename(newDir, name()); +} + +bool Maildir::rename(const QString &newName) +{ + if (name() == newName) + return true; + if (d->isRoot) + return false; // not (yet) supported + + QDir dir(d->path); + dir.cdUp(); + + return d->moveAndRename(dir, newName); +} + +QString Maildir::moveEntryTo(const QString &key, const Maildir &destination) +{ + const QString realKey(d->findRealKey(key)); + if (realKey.isEmpty()) { + qWarning() << "Unable to find: " << key; + // d->lastError = i18n("Cannot locate mail file %1." , key); + return QString(); + } + QFile f(realKey); + // ### is this safe regarding the maildir locking scheme? + const QString targetKey = destination.path() + QDir::separator() + QLatin1String("new") + QDir::separator() + key; + if (!f.rename(targetKey)) { + qDebug() << "Failed to rename" << realKey << "to" << targetKey << "! Error: " << f.errorString();; + d->lastError = f.errorString(); + return QString(); + } + + KeyCache* keyCache = KeyCache::self(); + + keyCache->addNewKey(destination.path(), key); + keyCache->removeKey(d->path, key); + + return key; +} + +QString Maildir::subDirPathForFolderPath(const QString &folderPath) +{ + QDir dir(folderPath); + const QString dirName = dir.dirName(); + dir.cdUp(); + return QFileInfo(dir, Private::subDirNameForFolderName(dirName)).filePath(); +} + +QString Maildir::subDirNameForFolderName(const QString &folderName) +{ + return Private::subDirNameForFolderName(folderName); +} + +void Maildir::removeCachedKeys(const QStringList & keys) +{ + KeyCache *keyCache = KeyCache::self(); + Q_FOREACH (const QString &key, keys) { + keyCache->removeKey(d->path, key); + } +} + +void Maildir::refreshKeyCache() +{ + KeyCache::self()->refreshKeys(d->path); +} + +QString Maildir::lastError() const +{ + return d->lastError; +} diff --git a/examples/maildirresource/libmaildir/maildir.h b/examples/maildirresource/libmaildir/maildir.h new file mode 100644 index 0000000..6853033 --- /dev/null +++ b/examples/maildirresource/libmaildir/maildir.h @@ -0,0 +1,266 @@ +/* + Copyright (c) 2007 Till Adam + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef MAILDIR_H +#define MAILDIR_H + + +#include "maildir_export.h" + +#include +#include + +class QDateTime; + +namespace KPIM { + +class MAILDIR_EXPORT Maildir +{ +public: + /** + Create a new Maildir object. + @param path The path to the maildir, if @p isRoot is @c false, that's the path + to the folder containing the cur/new/tmp folders, if @p isRoot is @c true this + is the path to a folder containing a number of maildirs. + @param isRoot Indicate whether this is a maildir containing mails and various + sub-folders or a container only containing maildirs. + */ + explicit Maildir( const QString& path = QString(), bool isRoot = false ); + /* Copy constructor */ + Maildir(const Maildir & rhs); + /* Copy operator */ + Maildir& operator=(const Maildir & rhs); + /** Equality comparison */ + bool operator==(const Maildir & rhs) const; + /* Destructor */ + ~Maildir(); + + /** Returns whether the maildir has all the necessary subdirectories, + * that they are readable, etc. + * @param createMissingFolders if true (the default), the cur/new/tmp folders are created if they are missing + */ + bool isValid( bool createMissingFolders = true ) const; + + /** + * Returns whether this is a normal maildir or a container containing maildirs. + */ + bool isRoot() const; + + /** + * Make a valid maildir at the path of this Maildir object. This involves + * creating the necessary subdirs, etc. Note that an empty Maildir is + * not valid, unless it is given valid path, or until create( ) is + * called on it. + */ + bool create(); + + /** + * Returns the path of this maildir. + */ + QString path() const; + + /** + * Returns the name of this maildir. + */ + QString name() const; + + /** + * Returns the list of items (mails) in the maildir. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList entryList() const; + + /** Returns the list of items (mails) in the maildirs "new" folder. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList listNew() const; + + /** Returns the list of items (mails) in the maildirs "cur" folder. These are keys, which + * map to filenames, internally, but that's an implementation detail, which + * should not be relied on. + */ + QStringList listCurrent() const; + + /** Return the path to the "new" directory */ + QString pathToNew() const; + + /** Return the path to the "cur" directory */ + QString pathToCurrent() const; + + /** + * Returns the full path to the subdir (the NAME.directory folder ). + **/ + QString subDirPath() const; + + /** + * Return the full path to the file identified by key (it can be either in the "new" or "cur" folder + **/ + QString findRealKey( const QString& key ) const; + + /** + * Returns the list of subfolders, as names (relative paths). Use the + * subFolder method to get Maildir objects representing them. + */ + QStringList subFolderList() const; + + /** + * Adds subfolder with the given @p folderName. + * @return an empty string on failure or the full path of the new subfolder + * on success + */ + QString addSubFolder( const QString& folderName ); + + /** + * Removes subfolder with the given @p folderName. Returns success or failure. + */ + bool removeSubFolder( const QString& folderName ); + + /** + * Returns a Maildir object for the given @p folderName. If such a folder + * exists, the Maildir object will be valid, otherwise you can call create() + * on it, to make a subfolder with that name. + */ + Maildir subFolder( const QString& folderName ) const; + + /** + * Returns the parent Maildir object for this Maildir, if there is one (ie. this is not the root). + */ + Maildir parent() const; + + /** + * Returns the size of the file in the maildir with the given @p key or \c -1 if key is not valid. + * @since 4.2 + */ + qint64 size( const QString& key ) const; + + /** + * Returns the modification time of the file in the maildir with the given @p key. + * @since 4.7 + */ + QDateTime lastModified( const QString &key ) const; + + /** + * Return the contents of the file in the maildir with the given @p key. + */ + QByteArray readEntry( const QString& key ) const; + + enum MailFlags { + Forwarded, + Replied, + Seen, + Flagged + }; + Q_DECLARE_FLAGS(Flags, MailFlags); + + /** + * Return the flags encoded in the maildir file name for an entry + **/ + Flags readEntryFlags( const QString& key ) const; + + /** + * Return the contents of the headers section of the file the maildir with the given @p file, that + * is a full path to the file. You can get it by using findRealKey(key) . + */ + QByteArray readEntryHeadersFromFile( const QString& file ) const; + + /** + * Return the contents of the headers section of the file the maildir with the given @p key. + */ + QByteArray readEntryHeaders( const QString& key ) const; + + /** + * Write the given @p data to a file in the maildir with the given @p key. + * Returns true in case of success, false in case of any error. + */ + bool writeEntry( const QString& key, const QByteArray& data ); + + /** + * Adds the given @p data to the maildir. Returns the key of the entry. + */ + QString addEntry( const QByteArray& data ); + + /** + * Removes the entry with the given @p key. Returns success or failure. + */ + bool removeEntry( const QString& key ); + + /** + * Change the flags for an entry specified by @p key. Returns the new key of the entry (the key might change because + * flags are stored in the unique filename). + */ + // QString changeEntryFlags( const QString& key, const Akonadi::Item::Flags& flags ); + + /** + * Moves this maildir into @p destination. + */ + bool moveTo( const Maildir &destination ); + + /** + * Renames this maildir to @p newName. + */ + bool rename( const QString &newName ); + + /** + * Moves the file with the given @p key into the Maildir @p destination. + * @returns The new file name inside @p destination. + */ + QString moveEntryTo( const QString& key, const KPIM::Maildir& destination ); + + /** + * Creates the maildir tree structure specific directory path that the + * given @p folderPath folder would have for its sub folders + * @param folderPath a maildir folder path + * @return the relative subDirPath for the given @p folderPath + * + * @see subDirNameForFolderName() + */ + static QString subDirPathForFolderPath( const QString &folderPath ); + + /** + * Creates the maildir tree structure specific directory name that the + * given @p folderName folder would have for its sub folders + * @param folderName a maildir folder name + * @return the relative subDirName for the given @p folderMame + * + * @see subDirPathForFolderPath() + */ + static QString subDirNameForFolderName( const QString &folderName ); + + /** Removes the listed keys from the key cache */ + void removeCachedKeys(const QStringList & keys); + + + /** Reloads the keys associated with the maildir in the key cache*/ + void refreshKeyCache(); + + /** Return the last error message string. The error might not come from the last performed operation, + if that was sucessful. The caller should always check the return value of the methods before + querying the last error string. */ + QString lastError() const; + +private: + void swap( const Maildir& ); + class Private; + Private *d; +}; + +} +#endif // __MAILDIR_H__ -- cgit v1.2.3