summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--common/domain/mail.fbs1
-rw-r--r--examples/CMakeLists.txt5
-rw-r--r--examples/maildirresource/CMakeLists.txt15
-rw-r--r--examples/maildirresource/domainadaptor.cpp35
-rw-r--r--examples/maildirresource/domainadaptor.h38
-rw-r--r--examples/maildirresource/facade.cpp41
-rw-r--r--examples/maildirresource/facade.h36
-rw-r--r--examples/maildirresource/libmaildir/CMakeLists.txt8
-rw-r--r--examples/maildirresource/libmaildir/keycache.cpp88
-rw-r--r--examples/maildirresource/libmaildir/keycache.h77
-rw-r--r--examples/maildirresource/libmaildir/maildir.cpp865
-rw-r--r--examples/maildirresource/libmaildir/maildir.h266
-rw-r--r--examples/maildirresource/maildirresource.cpp284
-rw-r--r--examples/maildirresource/maildirresource.h61
-rw-r--r--tests/CMakeLists.txt7
-rw-r--r--tests/maildirresourcetest.cpp66
17 files changed, 1893 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4c305e..c480ddd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,6 @@
1cmake_minimum_required(VERSION 2.8.12) 1cmake_minimum_required(VERSION 2.8.12)
2 2
3option(BUILD_MAILDIR "BUILD_MAILDIR" ON)
3 4
4# ECM setup 5# ECM setup
5find_package(ECM 0.0.10 REQUIRED NO_MODULE) 6find_package(ECM 0.0.10 REQUIRED NO_MODULE)
diff --git a/common/domain/mail.fbs b/common/domain/mail.fbs
index 13aa36d..17b29a0 100644
--- a/common/domain/mail.fbs
+++ b/common/domain/mail.fbs
@@ -9,6 +9,7 @@ table Mail {
9 date:string; 9 date:string;
10 unread:bool = false; 10 unread:bool = false;
11 important:bool = false; 11 important:bool = false;
12 mimeMessage:string;
12} 13}
13 14
14root_type Mail; 15root_type Mail;
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index ea2b0ce..837903a 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -3,3 +3,8 @@ add_subdirectory(client)
3 3
4# a simple dummy resource implementation 4# a simple dummy resource implementation
5add_subdirectory(dummyresource) 5add_subdirectory(dummyresource)
6
7if (BUILD_MAILDIR)
8 # a maildir resource implementation
9 add_subdirectory(maildirresource)
10endif()
diff --git a/examples/maildirresource/CMakeLists.txt b/examples/maildirresource/CMakeLists.txt
new file mode 100644
index 0000000..2340cf6
--- /dev/null
+++ b/examples/maildirresource/CMakeLists.txt
@@ -0,0 +1,15 @@
1project(akonadi2_resource_maildir)
2
3add_definitions(-DQT_PLUGIN)
4include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
5
6find_package(KF5 COMPONENTS REQUIRED Mime)
7
8add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp)
9# generate_flatbuffers(${PROJECT_NAME} dummycalendar)
10qt5_use_modules(${PROJECT_NAME} Core Network)
11target_link_libraries(${PROJECT_NAME} akonadi2common maildir KF5::Mime)
12
13install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${AKONADI2_RESOURCE_PLUGINS_PATH})
14
15add_subdirectory(libmaildir)
diff --git a/examples/maildirresource/domainadaptor.cpp b/examples/maildirresource/domainadaptor.cpp
new file mode 100644
index 0000000..71b2354
--- /dev/null
+++ b/examples/maildirresource/domainadaptor.cpp
@@ -0,0 +1,35 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "domainadaptor.h"
21
22using namespace flatbuffers;
23
24MaildirMailAdaptorFactory::MaildirMailAdaptorFactory()
25 : DomainTypeAdaptorFactory()
26{
27
28}
29
30MaildirFolderAdaptorFactory::MaildirFolderAdaptorFactory()
31 : DomainTypeAdaptorFactory()
32{
33
34}
35
diff --git a/examples/maildirresource/domainadaptor.h b/examples/maildirresource/domainadaptor.h
new file mode 100644
index 0000000..0fc7108
--- /dev/null
+++ b/examples/maildirresource/domainadaptor.h
@@ -0,0 +1,38 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19#pragma once
20
21#include <common/domainadaptor.h>
22#include "mail_generated.h"
23#include "folder_generated.h"
24#include "dummy_generated.h"
25
26class MaildirMailAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Mail, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder>
27{
28public:
29 MaildirMailAdaptorFactory();
30 virtual ~MaildirMailAdaptorFactory() {};
31};
32
33class MaildirFolderAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Folder, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder>
34{
35public:
36 MaildirFolderAdaptorFactory();
37 virtual ~MaildirFolderAdaptorFactory() {};
38};
diff --git a/examples/maildirresource/facade.cpp b/examples/maildirresource/facade.cpp
new file mode 100644
index 0000000..3cf3fde
--- /dev/null
+++ b/examples/maildirresource/facade.cpp
@@ -0,0 +1,41 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "facade.h"
21
22#include "domainadaptor.h"
23
24MaildirResourceMailFacade::MaildirResourceMailFacade(const QByteArray &instanceIdentifier)
25 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<MaildirMailAdaptorFactory>::create())
26{
27}
28
29MaildirResourceMailFacade::~MaildirResourceMailFacade()
30{
31}
32
33
34MaildirResourceFolderFacade::MaildirResourceFolderFacade(const QByteArray &instanceIdentifier)
35 : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<MaildirFolderAdaptorFactory>::create())
36{
37}
38
39MaildirResourceFolderFacade::~MaildirResourceFolderFacade()
40{
41}
diff --git a/examples/maildirresource/facade.h b/examples/maildirresource/facade.h
new file mode 100644
index 0000000..80f0d06
--- /dev/null
+++ b/examples/maildirresource/facade.h
@@ -0,0 +1,36 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#pragma once
21
22#include "common/facade.h"
23
24class MaildirResourceMailFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>
25{
26public:
27 MaildirResourceMailFacade(const QByteArray &instanceIdentifier);
28 virtual ~MaildirResourceMailFacade();
29};
30
31class MaildirResourceFolderFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>
32{
33public:
34 MaildirResourceFolderFacade(const QByteArray &instanceIdentifier);
35 virtual ~MaildirResourceFolderFacade();
36};
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 @@
1# add_subdirectory( tests )
2
3set(maildir_LIB_SRCS keycache.cpp maildir.cpp)
4
5add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS})
6qt5_use_modules(maildir Core Network)
7# set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} )
8install(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 @@
1/*
2 Copyright (C) 2012 Andras Mantia <amantia@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18
19
20#include "keycache.h"
21
22#include <QDir>
23
24KeyCache* KeyCache::mSelf = 0;
25
26void KeyCache::addKeys( const QString& dir )
27{
28 if ( !mNewKeys.contains( dir ) ) {
29 mNewKeys.insert( dir, listNew( dir ) );
30 //kDebug() << "Added new keys for: " << dir;
31 }
32
33 if ( !mCurKeys.contains( dir ) ) {
34 mCurKeys.insert( dir, listCurrent( dir ) );
35 //kDebug() << "Added cur keys for: " << dir;
36 }
37}
38
39void KeyCache::refreshKeys( const QString& dir )
40{
41 mNewKeys.remove( dir );
42 mCurKeys.remove( dir );
43 addKeys( dir );
44}
45
46void KeyCache::addNewKey( const QString& dir, const QString& key )
47{
48 mNewKeys[dir].insert( key );
49 // kDebug() << "Added new key for : " << dir << " key: " << key;
50}
51
52void KeyCache::addCurKey( const QString& dir, const QString& key )
53{
54 mCurKeys[dir].insert( key );
55 // kDebug() << "Added cur key for : " << dir << " key:" << key;
56}
57
58void KeyCache::removeKey( const QString& dir, const QString& key )
59{
60 //kDebug() << "Removed new and cur key for: " << dir << " key:" << key;
61 mNewKeys[dir].remove( key );
62 mCurKeys[dir].remove( key );
63}
64
65bool KeyCache::isCurKey( const QString& dir, const QString& key ) const
66{
67 return mCurKeys.value( dir ).contains( key );
68}
69
70bool KeyCache::isNewKey( const QString& dir, const QString& key ) const
71{
72 return mNewKeys.value( dir ).contains( key );
73}
74
75QSet< QString > KeyCache::listNew( const QString& dir ) const
76{
77 QDir d( dir + QString::fromLatin1( "/new" ) );
78 d.setSorting(QDir::NoSort);
79 return d.entryList( QDir::Files ).toSet();
80}
81
82QSet< QString > KeyCache::listCurrent( const QString& dir ) const
83{
84 QDir d( dir + QString::fromLatin1( "/cur" ) );
85 d.setSorting(QDir::NoSort);
86 return d.entryList( QDir::Files ).toSet();
87}
88
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 @@
1/*
2 Copyright (C) 2012 Andras Mantia <amantia@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18
19
20#ifndef KEYCACHE_H
21#define KEYCACHE_H
22
23/** @brief a cache for the maildir keys (file names in cur/new folders).
24 * It is used to find if a file is in cur or new
25 */
26
27#include <QSet>
28#include <QHash>
29
30class KeyCache {
31
32public:
33 static KeyCache *self()
34 {
35 if ( !mSelf )
36 mSelf = new KeyCache();
37 return mSelf;
38 }
39
40 /** Find the new and cur keys on the disk for @param dir and add them to the cache */
41 void addKeys( const QString& dir );
42
43 /** Refresh the new and cur keys for @param dir */
44 void refreshKeys( const QString& dir );
45
46 /** Add a "new" key for @param dir. */
47 void addNewKey( const QString& dir, const QString& key );
48
49 /** Add a "cur" key for @param dir. */
50 void addCurKey( const QString& dir, const QString& key );
51
52 /** Remove all keys associated with @param dir. */
53 void removeKey( const QString& dir, const QString& key );
54
55 /** Check if the @param key is a "cur" key in @param dir */
56 bool isCurKey( const QString& dir, const QString& key ) const;
57
58 /** Check if the @param key is a "new" key in @param dir */
59 bool isNewKey( const QString& dir, const QString& key ) const;
60
61private:
62 KeyCache() {
63 }
64
65 QSet<QString> listNew( const QString& dir ) const;
66
67 QSet<QString> listCurrent( const QString& dir ) const;
68
69 QHash< QString, QSet<QString> > mNewKeys;
70 QHash< QString, QSet<QString> > mCurKeys;
71
72 static KeyCache* mSelf;
73
74};
75
76
77#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 @@
1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "maildir.h"
21#include "keycache.h"
22
23#include <QDateTime>
24#include <QDir>
25#include <QFileInfo>
26#include <QHostInfo>
27#include <QUuid>
28
29#include <time.h>
30#include <unistd.h>
31
32//Define it to get more debug output to expense of operating speed
33// #define DEBUG_KEYCACHE_CONSITENCY
34
35
36// static void initRandomSeed()
37// {
38// static bool init = false;
39// if (!init) {
40// unsigned int seed;
41// init = true;
42// int fd = KDE_open("/dev/urandom", O_RDONLY);
43// if (fd < 0 || ::read(fd, &seed, sizeof(seed)) != sizeof(seed)) {
44// // No /dev/urandom... try something else.
45// srand(getpid());
46// seed = rand() + time(0);
47// }
48//
49// if (fd >= 0)
50// close(fd);
51//
52// qsrand(seed);
53// }
54// }
55
56
57bool removeDirAndContentsRecursively(const QString & path)
58{
59 bool success = true;
60
61 QDir d;
62 d.setPath(path);
63 d.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks);
64
65 QFileInfoList list = d.entryInfoList();
66
67 Q_FOREACH (const QFileInfo &fi, list) {
68 if (fi.isDir()) {
69 if (fi.fileName() != QLatin1String(".") && fi.fileName() != QLatin1String("..")) {
70 success = success && removeDirAndContentsRecursively(fi.absoluteFilePath());
71 }
72 } else {
73 success = success && d.remove(fi.absoluteFilePath());
74 }
75 }
76
77 if (success) {
78 success = success && d.rmdir(path); // nuke ourselves, we should be empty now
79 }
80 return success;
81}
82
83using namespace KPIM;
84
85Q_GLOBAL_STATIC_WITH_ARGS(QRegExp, statusSeparatorRx, (":|!"))
86
87class Maildir::Private
88{
89public:
90 Private(const QString& p, bool isRoot)
91 :path(p), isRoot(isRoot)
92 {
93 hostName = QHostInfo::localHostName();
94 // The default implementation of QUuid::createUuid() doesn't use
95 // a seed that is random enough. Therefor we use our own initialization
96 // until this issue will be fixed in Qt 4.7.
97 // initRandomSeed();
98
99 //Cache object is created the first time this runs.
100 //It will live throughout the lifetime of the application
101 KeyCache::self()->addKeys(path);
102 }
103
104 Private(const Private& rhs)
105 {
106 path = rhs.path;
107 isRoot = rhs.isRoot;
108 hostName = rhs.hostName;
109 }
110
111 bool operator==(const Private& rhs) const
112 {
113 return path == rhs.path;
114 }
115 bool accessIsPossible(bool createMissingFolders = true);
116 bool canAccess(const QString& path) const;
117
118 QStringList subPaths() const
119 {
120 QStringList paths;
121 paths << path + QString::fromLatin1("/cur");
122 paths << path + QString::fromLatin1("/new");
123 paths << path + QString::fromLatin1("/tmp");
124 return paths;
125 }
126
127 QStringList listNew() const
128 {
129 QDir d(path + QString::fromLatin1("/new"));
130 d.setSorting(QDir::NoSort);
131 return d.entryList(QDir::Files);
132 }
133
134 QStringList listCurrent() const
135 {
136 QDir d(path + QString::fromLatin1("/cur"));
137 d.setSorting(QDir::NoSort);
138 return d.entryList(QDir::Files);
139 }
140
141 QString findRealKey(const QString& key) const
142 {
143 KeyCache* keyCache = KeyCache::self();
144 if (keyCache->isNewKey(path, key)) {
145#ifdef DEBUG_KEYCACHE_CONSITENCY
146 if (!QFile::exists(path + QString::fromLatin1("/new/") + key)) {
147 qDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/new/") + key;
148 }
149#endif
150 return path + QString::fromLatin1("/new/") + key;
151 }
152 if (keyCache->isCurKey(path, key)) {
153#ifdef DEBUG_KEYCACHE_CONSITENCY
154 if (!QFile::exists(path + QString::fromLatin1("/cur/") + key)) {
155 qDebug() << "WARNING: key is in cache, but the file is gone: " << path + QString::fromLatin1("/cur/") + key;
156 }
157#endif
158 return path + QString::fromLatin1("/cur/") + key;
159 }
160 QString realKey = path + QString::fromLatin1("/new/") + key;
161
162 QFile f(realKey);
163 if (f.exists()) {
164 keyCache->addNewKey(path, key);
165 } else { //not in "new", search in "cur"
166 realKey = path + QString::fromLatin1("/cur/") + key;
167 QFile f2(realKey);
168 if (f2.exists()) {
169 keyCache->addCurKey(path, key);
170 } else {
171 realKey.clear(); //not in "cur" either
172 }
173 }
174
175 return realKey;
176 }
177
178 static QString subDirNameForFolderName(const QString &folderName)
179 {
180 return QString::fromLatin1(".%1.directory").arg(folderName);
181 }
182
183 QString subDirPath() const
184 {
185 QDir dir(path);
186 return subDirNameForFolderName(dir.dirName());
187 }
188
189 bool moveAndRename(QDir &dest, const QString &newName)
190 {
191 if (!dest.exists()) {
192 qDebug() << "Destination does not exist";
193 return false;
194 }
195 if (dest.exists(newName) || dest.exists(subDirNameForFolderName(newName))) {
196 qDebug() << "New name already in use";
197 return false;
198 }
199
200 if (!dest.rename(path, newName)) {
201 qDebug() << "Failed to rename maildir";
202 return false;
203 }
204 const QDir subDirs(Maildir::subDirPathForFolderPath(path));
205 if (subDirs.exists() && !dest.rename(subDirs.path(), subDirNameForFolderName(newName))) {
206 qDebug() << "Failed to rename subfolders";
207 return false;
208 }
209
210 path = dest.path() + QDir::separator() + newName;
211 return true;
212 }
213
214 QString path;
215 bool isRoot;
216 QString hostName;
217 QString lastError;
218};
219
220Maildir::Maildir(const QString& path, bool isRoot)
221:d(new Private(path, isRoot))
222{
223}
224
225void Maildir::swap(const Maildir &rhs)
226{
227 Private *p = d;
228 d = new Private(*rhs.d);
229 delete p;
230}
231
232
233Maildir::Maildir(const Maildir & rhs)
234 :d(new Private(*rhs.d))
235
236{
237}
238
239Maildir& Maildir::operator= (const Maildir & rhs)
240{
241 // copy and swap, exception safe, and handles assignment to self
242 Maildir temp(rhs);
243 swap(temp);
244 return *this;
245}
246
247
248bool Maildir::operator== (const Maildir & rhs) const
249{
250 return *d == *rhs.d;
251}
252
253
254Maildir::~Maildir()
255{
256 delete d;
257}
258
259bool Maildir::Private::canAccess(const QString& path) const
260{
261 //return access(QFile::encodeName(path), R_OK | W_OK | X_OK) != 0;
262 // FIXME X_OK?
263 QFileInfo d(path);
264 return d.isReadable() && d.isWritable();
265}
266
267bool Maildir::Private::accessIsPossible(bool createMissingFolders)
268{
269 QStringList paths = subPaths();
270
271 paths.prepend(path);
272
273 Q_FOREACH (const QString &p, paths) {
274 if (!QFile::exists(p)) {
275 if (!createMissingFolders) {
276 // lastError = i18n("Error opening %1; this folder is missing.", p);
277 return false;
278 }
279 QDir().mkpath(p);
280 if (!QFile::exists(p)) {
281 // lastError = i18n("Error opening %1; this folder is missing.", p);
282 return false;
283 }
284 }
285 if (!canAccess(p)) {
286 // lastError = i18n("Error opening %1; either this is not a valid "
287 // "maildir folder, or you do not have sufficient access permissions." ,p);
288 return false;
289 }
290 }
291 return true;
292}
293
294bool Maildir::isValid(bool createMissingFolders) const
295{
296 if (path().isEmpty()) {
297 return false;
298 }
299 if (!d->isRoot) {
300 if (d->accessIsPossible(createMissingFolders)) {
301 return true;
302 }
303 } else {
304 Q_FOREACH (const QString &sf, subFolderList()) {
305 const Maildir subMd = Maildir(path() + QLatin1Char('/') + sf);
306 if (!subMd.isValid()) {
307 d->lastError = subMd.lastError();
308 return false;
309 }
310 }
311 return true;
312 }
313 return false;
314}
315
316bool Maildir::isRoot() const
317{
318 return d->isRoot;
319}
320
321bool Maildir::create()
322{
323 // FIXME: in a failure case, this will leave partially created dirs around
324 // we should clean them up, but only if they didn't previously existed...
325 Q_FOREACH (const QString &p, d->subPaths()) {
326 QDir dir(p);
327 if (!dir.exists(p)) {
328 if (!dir.mkpath(p))
329 return false;
330 }
331 }
332 return true;
333}
334
335QString Maildir::path() const
336{
337 return d->path;
338}
339
340QString Maildir::name() const
341{
342 const QDir dir(d->path);
343 return dir.dirName();
344}
345
346QString Maildir::addSubFolder(const QString& path)
347{
348 if (!isValid())
349 return QString();
350
351 // make the subdir dir
352 QDir dir(d->path);
353 if (!d->isRoot) {
354 dir.cdUp();
355 if (!dir.exists(d->subDirPath()))
356 dir.mkdir(d->subDirPath());
357 dir.cd(d->subDirPath());
358 }
359
360 const QString fullPath = dir.path() + QLatin1Char('/') + path;
361 Maildir subdir(fullPath);
362 if (subdir.create())
363 return fullPath;
364 return QString();
365}
366
367bool Maildir::removeSubFolder(const QString& folderName)
368{
369 if (!isValid()) return false;
370 QDir dir(d->path);
371 if (!d->isRoot) {
372 dir.cdUp();
373 if (!dir.exists(d->subDirPath())) return false;
374 dir.cd(d->subDirPath());
375 }
376 if (!dir.exists(folderName)) return false;
377
378 // remove it recursively
379 bool result = removeDirAndContentsRecursively(dir.absolutePath() + QLatin1Char('/') + folderName);
380 QString subfolderName = subDirNameForFolderName(folderName);
381 if (dir.exists(subfolderName))
382 result &= removeDirAndContentsRecursively(dir.absolutePath() + QLatin1Char('/') + subfolderName);
383 return result;
384}
385
386Maildir Maildir::subFolder(const QString& subFolder) const
387{
388 // make the subdir dir
389 QDir dir(d->path);
390 if (!d->isRoot) {
391 dir.cdUp();
392 if (dir.exists(d->subDirPath())) {
393 dir.cd(d->subDirPath());
394 }
395 }
396 return Maildir(dir.path() + QLatin1Char('/') + subFolder);
397}
398
399Maildir Maildir::parent() const
400{
401 if (!isValid() || d->isRoot)
402 return Maildir();
403 QDir dir(d->path);
404 dir.cdUp();
405 if (!dir.dirName().startsWith(QLatin1Char('.')) || !dir.dirName().endsWith(QLatin1String(".directory")))
406 return Maildir();
407 const QString parentName = dir.dirName().mid(1, dir.dirName().size() - 11);
408 dir.cdUp();
409 dir.cd(parentName);
410 return Maildir (dir.path());
411}
412
413QStringList Maildir::entryList() const
414{
415 QStringList result;
416 if (isValid()) {
417 result += d->listNew();
418 result += d->listCurrent();
419 }
420 // qDebug() <<"Maildir::entryList()" << result;
421 return result;
422}
423
424QStringList Maildir::listCurrent() const
425{
426 QStringList result;
427 if (isValid()) {
428 result += d->listCurrent();
429 }
430 return result;
431}
432
433QString Maildir::findRealKey(const QString& key) const
434{
435 return d->findRealKey(key);
436}
437
438
439QStringList Maildir::listNew() const
440{
441 QStringList result;
442 if (isValid()) {
443 result += d->listNew();
444 }
445 return result;
446}
447
448QString Maildir::pathToNew() const
449{
450 if (isValid()) {
451 return d->path + QString::fromLatin1("/new");
452 }
453 return QString();
454}
455
456QString Maildir::pathToCurrent() const
457{
458 if (isValid()) {
459 return d->path + QString::fromLatin1("/cur");
460 }
461 return QString();
462}
463
464QString Maildir::subDirPath() const
465{
466 QDir dir(d->path);
467 dir.cdUp();
468 return dir.path() + QDir::separator() + d->subDirPath();
469}
470
471
472
473QStringList Maildir::subFolderList() const
474{
475 QDir dir(d->path);
476
477 // the root maildir has its subfolders directly beneath it
478 if (!d->isRoot) {
479 dir.cdUp();
480 if (!dir.exists(d->subDirPath()))
481 return QStringList();
482 dir.cd(d->subDirPath());
483 }
484 dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
485 QStringList entries = dir.entryList();
486 entries.removeAll(QLatin1String("cur"));
487 entries.removeAll(QLatin1String("new"));
488 entries.removeAll(QLatin1String("tmp"));
489 return entries;
490}
491
492QByteArray Maildir::readEntry(const QString& key) const
493{
494 QByteArray result;
495
496 QString realKey(d->findRealKey(key));
497 if (realKey.isEmpty()) {
498 // FIXME error handling?
499 qWarning() << "Maildir::readEntry unable to find: " << key;
500 // d->lastError = i18n("Cannot locate mail file %1." ,key);
501 return result;
502 }
503
504 QFile f(realKey);
505 if (!f.open(QIODevice::ReadOnly)) {
506 // d->lastError = i18n("Cannot open mail file %1.", realKey);
507 return result;
508 }
509
510 // FIXME be safer than this
511 result = f.readAll();
512
513 return result;
514}
515qint64 Maildir::size(const QString& key) const
516{
517 QString realKey(d->findRealKey(key));
518 if (realKey.isEmpty()) {
519 // FIXME error handling?
520 qWarning() << "Maildir::size unable to find: " << key;
521 // d->lastError = i18n("Cannot locate mail file %1." , key);
522 return -1;
523 }
524
525 QFileInfo info(realKey);
526 if (!info.exists()) {
527 // d->lastError = i18n("Cannot open mail file %1." ,realKey);
528 return -1;
529 }
530
531 return info.size();
532}
533
534QDateTime Maildir::lastModified(const QString& key) const
535{
536 const QString realKey(d->findRealKey(key));
537 if (realKey.isEmpty()) {
538 qWarning() << "Maildir::lastModified unable to find: " << key;
539 // d->lastError = i18n("Cannot locate mail file %1." , key);
540 return QDateTime();
541 }
542
543 const QFileInfo info(realKey);
544 if (!info.exists())
545 return QDateTime();
546
547 return info.lastModified();
548}
549
550QByteArray Maildir::readEntryHeadersFromFile(const QString& file) const
551{
552 QByteArray result;
553
554 QFile f(file);
555 if (!f.open(QIODevice::ReadOnly)) {
556 // FIXME error handling?
557 qWarning() << "Maildir::readEntryHeaders unable to find: " << file;
558 // d->lastError = i18n("Cannot locate mail file %1." , file);
559 return result;
560 }
561 f.map(0, qMin((qint64)8000, f.size()));
562 forever {
563 QByteArray line = f.readLine();
564 if (line.isEmpty() || line.startsWith('\n'))
565 break;
566 result.append(line);
567 }
568 return result;
569}
570
571QByteArray Maildir::readEntryHeaders(const QString& key) const
572{
573 const QString realKey(d->findRealKey(key));
574 if (realKey.isEmpty()) {
575 qWarning() << "Maildir::readEntryHeaders unable to find: " << key;
576 // d->lastError = i18n("Cannot locate mail file %1." , key);
577 return QByteArray();
578 }
579
580 return readEntryHeadersFromFile(realKey);
581}
582
583
584static QString createUniqueFileName()
585{
586 qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000;
587 int r = qrand() % 1000;
588 QString identifier = QLatin1String("R") + QString::number(r);
589
590 QString fileName = QString::number(time) + QLatin1Char('.') + identifier + QLatin1Char('.');
591
592 return fileName;
593}
594
595bool Maildir::writeEntry(const QString& key, const QByteArray& data)
596{
597 QString realKey(d->findRealKey(key));
598 if (realKey.isEmpty()) {
599 // FIXME error handling?
600 qWarning() << "Maildir::writeEntry unable to find: " << key;
601 // d->lastError = i18n("Cannot locate mail file %1." ,key);
602 return false;
603 }
604 QFile f(realKey);
605 bool result = f.open(QIODevice::WriteOnly);
606 result = result & (f.write(data) != -1);
607 f.close();
608 if (!result) {
609 // d->lastError = i18n("Cannot write to mail file %1." ,realKey);
610 return false;
611 }
612 return true;
613}
614
615QString Maildir::addEntry(const QByteArray& data)
616{
617 QString uniqueKey;
618 QString key;
619 QString finalKey;
620 QString curKey;
621
622 // QUuid doesn't return globally unique identifiers, therefor we query until we
623 // get one that doesn't exists yet
624 do {
625 uniqueKey = createUniqueFileName() + d->hostName;
626 key = d->path + QLatin1String("/tmp/") + uniqueKey;
627 finalKey = d->path + QLatin1String("/new/") + uniqueKey;
628 curKey = d->path + QLatin1String("/cur/") + uniqueKey;
629 } while (QFile::exists(key) || QFile::exists(finalKey) || QFile::exists(curKey));
630
631 QFile f(key);
632 bool result = f.open(QIODevice::WriteOnly);
633 result = result & (f.write(data) != -1);
634 f.close();
635 if (!result) {
636 // d->lastError = i18n("Cannot write to mail file %1." , key);
637 return QString();
638 }
639 /*
640 * FIXME:
641 *
642 * The whole point of the locking free maildir idea is that the moves between
643 * the internal directories are atomic. Afaik QFile::rename does not guarantee
644 * that, so this will need to be done properly. - ta
645 *
646 * For reference: http://trolltech.com/developer/task-tracker/index_html?method=entry&id=211215
647 */
648 if (!f.rename(finalKey)) {
649 qWarning() << "Maildir: Failed to add entry: " << finalKey << "! Error: " << f.errorString();
650 // d->lastError = i18n("Failed to create mail file %1. The error was: %2" , finalKey, f.errorString());
651 return QString();
652 }
653 KeyCache *keyCache = KeyCache::self();
654 keyCache->removeKey(d->path, key); //remove all keys, be it "cur" or "new" first
655 keyCache->addNewKey(d->path, key); //and add a key for "new", as the mail was moved there
656 return uniqueKey;
657}
658
659bool Maildir::removeEntry(const QString& key)
660{
661 QString realKey(d->findRealKey(key));
662 if (realKey.isEmpty()) {
663 qWarning() << "Maildir::removeEntry unable to find: " << key;
664 return false;
665 }
666 KeyCache *keyCache = KeyCache::self();
667 keyCache->removeKey(d->path, key);
668 return QFile::remove(realKey);
669}
670
671// QString Maildir::changeEntryFlags(const QString& key, const Akonadi::Item::Flags& flags)
672// {
673// QString realKey(d->findRealKey(key));
674// if (realKey.isEmpty()) {
675// qWarning() << "Maildir::changeEntryFlags unable to find: " << key;
676// d->lastError = i18n("Cannot locate mail file %1." , key);
677// return QString();
678// }
679//
680// const QRegExp rx = *(statusSeparatorRx());
681// QString finalKey = key.left(key.indexOf(rx));
682//
683// QStringList mailDirFlags;
684// Q_FOREACH (const Akonadi::Item::Flag &flag, flags) {
685// if (flag == Akonadi::MessageFlags::Forwarded)
686// mailDirFlags << QLatin1String("P");
687// if (flag == Akonadi::MessageFlags::Replied)
688// mailDirFlags << QLatin1String("R");
689// if (flag == Akonadi::MessageFlags::Seen)
690// mailDirFlags << QLatin1String("S");
691// if (flag == Akonadi::MessageFlags::Deleted)
692// mailDirFlags << QLatin1String("T");
693// if (flag == Akonadi::MessageFlags::Flagged)
694// mailDirFlags << QLatin1String("F");
695// }
696// mailDirFlags.sort();
697// if (!mailDirFlags.isEmpty()) {
698// #ifdef Q_OS_WIN
699// finalKey.append(QLatin1String("!2,") + mailDirFlags.join(QString()));
700// #else
701// finalKey.append(QLatin1String(":2,") + mailDirFlags.join(QString()));
702// #endif
703// }
704//
705// QString newUniqueKey = finalKey; //key without path
706// finalKey.prepend(d->path + QString::fromLatin1("/cur/"));
707//
708// if (realKey == finalKey) {
709// // Somehow it already is named this way (e.g. migration bug -> wrong status in akonadi)
710// return newUniqueKey;
711// }
712//
713// QFile f(realKey);
714// if (QFile::exists(finalKey)) {
715// QFile destFile(finalKey);
716// QByteArray destContent;
717// if (destFile.open(QIODevice::ReadOnly)) {
718// destContent = destFile.readAll();
719// destFile.close();
720// }
721// QByteArray sourceContent;
722// if (f.open(QIODevice::ReadOnly)) {
723// sourceContent = f.readAll();
724// f.close();
725// }
726//
727// if (destContent != sourceContent) {
728// QString newFinalKey = QLatin1String("1-") + newUniqueKey;
729// int i = 1;
730// while (QFile::exists(d->path + QString::fromLatin1("/cur/") + newFinalKey)) {
731// i++;
732// newFinalKey = QString::number(i) + QLatin1Char('-') + newUniqueKey;
733// }
734// finalKey = d->path + QString::fromLatin1("/cur/") + newFinalKey;
735// } else {
736// QFile::remove(finalKey); //they are the same
737// }
738// }
739//
740// if (!f.rename(finalKey)) {
741// qWarning() << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString();
742// d->lastError = i18n("Failed to update the file name %1 to %2 on the disk. The error was: %3." , f.fileName(), finalKey, f.errorString());
743// return QString();
744// }
745//
746// KeyCache *keyCache = KeyCache::self();
747// keyCache->removeKey(d->path, key);
748// keyCache->addCurKey(d->path, newUniqueKey);
749//
750// return newUniqueKey;
751// }
752//
753Maildir::Flags Maildir::readEntryFlags(const QString& key) const
754{
755 Flags flags;
756 const QRegExp rx = *(statusSeparatorRx());
757 const int index = key.indexOf(rx);
758 if (index != -1) {
759 const QString mailDirFlags = key.mid(index + 3); // after "(:|!)2,"
760 const int flagSize(mailDirFlags.size());
761 for (int i = 0; i < flagSize; ++i) {
762 if (mailDirFlags[i] == QLatin1Char('P'))
763 flags |= Forwarded;
764 else if (mailDirFlags[i] == QLatin1Char('R'))
765 flags |= Replied;
766 else if (mailDirFlags[i] == QLatin1Char('S'))
767 flags |= Seen;
768 else if (mailDirFlags[i] == QLatin1Char('F'))
769 flags |= Flagged;
770 }
771 }
772
773 return flags;
774}
775
776bool Maildir::moveTo(const Maildir &newParent)
777{
778 if (d->isRoot)
779 return false; // not supported
780
781 QDir newDir(newParent.path());
782 if (!newParent.d->isRoot) {
783 newDir.cdUp();
784 if (!newDir.exists(newParent.d->subDirPath()))
785 newDir.mkdir(newParent.d->subDirPath());
786 newDir.cd(newParent.d->subDirPath());
787 }
788
789 QDir currentDir(d->path);
790 currentDir.cdUp();
791
792 if (newDir == currentDir)
793 return true;
794
795 return d->moveAndRename(newDir, name());
796}
797
798bool Maildir::rename(const QString &newName)
799{
800 if (name() == newName)
801 return true;
802 if (d->isRoot)
803 return false; // not (yet) supported
804
805 QDir dir(d->path);
806 dir.cdUp();
807
808 return d->moveAndRename(dir, newName);
809}
810
811QString Maildir::moveEntryTo(const QString &key, const Maildir &destination)
812{
813 const QString realKey(d->findRealKey(key));
814 if (realKey.isEmpty()) {
815 qWarning() << "Unable to find: " << key;
816 // d->lastError = i18n("Cannot locate mail file %1." , key);
817 return QString();
818 }
819 QFile f(realKey);
820 // ### is this safe regarding the maildir locking scheme?
821 const QString targetKey = destination.path() + QDir::separator() + QLatin1String("new") + QDir::separator() + key;
822 if (!f.rename(targetKey)) {
823 qDebug() << "Failed to rename" << realKey << "to" << targetKey << "! Error: " << f.errorString();;
824 d->lastError = f.errorString();
825 return QString();
826 }
827
828 KeyCache* keyCache = KeyCache::self();
829
830 keyCache->addNewKey(destination.path(), key);
831 keyCache->removeKey(d->path, key);
832
833 return key;
834}
835
836QString Maildir::subDirPathForFolderPath(const QString &folderPath)
837{
838 QDir dir(folderPath);
839 const QString dirName = dir.dirName();
840 dir.cdUp();
841 return QFileInfo(dir, Private::subDirNameForFolderName(dirName)).filePath();
842}
843
844QString Maildir::subDirNameForFolderName(const QString &folderName)
845{
846 return Private::subDirNameForFolderName(folderName);
847}
848
849void Maildir::removeCachedKeys(const QStringList & keys)
850{
851 KeyCache *keyCache = KeyCache::self();
852 Q_FOREACH (const QString &key, keys) {
853 keyCache->removeKey(d->path, key);
854 }
855}
856
857void Maildir::refreshKeyCache()
858{
859 KeyCache::self()->refreshKeys(d->path);
860}
861
862QString Maildir::lastError() const
863{
864 return d->lastError;
865}
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 @@
1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#ifndef MAILDIR_H
21#define MAILDIR_H
22
23
24#include "maildir_export.h"
25
26#include <QString>
27#include <QStringList>
28
29class QDateTime;
30
31namespace KPIM {
32
33class MAILDIR_EXPORT Maildir
34{
35public:
36 /**
37 Create a new Maildir object.
38 @param path The path to the maildir, if @p isRoot is @c false, that's the path
39 to the folder containing the cur/new/tmp folders, if @p isRoot is @c true this
40 is the path to a folder containing a number of maildirs.
41 @param isRoot Indicate whether this is a maildir containing mails and various
42 sub-folders or a container only containing maildirs.
43 */
44 explicit Maildir( const QString& path = QString(), bool isRoot = false );
45 /* Copy constructor */
46 Maildir(const Maildir & rhs);
47 /* Copy operator */
48 Maildir& operator=(const Maildir & rhs);
49 /** Equality comparison */
50 bool operator==(const Maildir & rhs) const;
51 /* Destructor */
52 ~Maildir();
53
54 /** Returns whether the maildir has all the necessary subdirectories,
55 * that they are readable, etc.
56 * @param createMissingFolders if true (the default), the cur/new/tmp folders are created if they are missing
57 */
58 bool isValid( bool createMissingFolders = true ) const;
59
60 /**
61 * Returns whether this is a normal maildir or a container containing maildirs.
62 */
63 bool isRoot() const;
64
65 /**
66 * Make a valid maildir at the path of this Maildir object. This involves
67 * creating the necessary subdirs, etc. Note that an empty Maildir is
68 * not valid, unless it is given valid path, or until create( ) is
69 * called on it.
70 */
71 bool create();
72
73 /**
74 * Returns the path of this maildir.
75 */
76 QString path() const;
77
78 /**
79 * Returns the name of this maildir.
80 */
81 QString name() const;
82
83 /**
84 * Returns the list of items (mails) in the maildir. These are keys, which
85 * map to filenames, internally, but that's an implementation detail, which
86 * should not be relied on.
87 */
88 QStringList entryList() const;
89
90 /** Returns the list of items (mails) in the maildirs "new" folder. These are keys, which
91 * map to filenames, internally, but that's an implementation detail, which
92 * should not be relied on.
93 */
94 QStringList listNew() const;
95
96 /** Returns the list of items (mails) in the maildirs "cur" folder. These are keys, which
97 * map to filenames, internally, but that's an implementation detail, which
98 * should not be relied on.
99 */
100 QStringList listCurrent() const;
101
102 /** Return the path to the "new" directory */
103 QString pathToNew() const;
104
105 /** Return the path to the "cur" directory */
106 QString pathToCurrent() const;
107
108 /**
109 * Returns the full path to the subdir (the NAME.directory folder ).
110 **/
111 QString subDirPath() const;
112
113 /**
114 * Return the full path to the file identified by key (it can be either in the "new" or "cur" folder
115 **/
116 QString findRealKey( const QString& key ) const;
117
118 /**
119 * Returns the list of subfolders, as names (relative paths). Use the
120 * subFolder method to get Maildir objects representing them.
121 */
122 QStringList subFolderList() const;
123
124 /**
125 * Adds subfolder with the given @p folderName.
126 * @return an empty string on failure or the full path of the new subfolder
127 * on success
128 */
129 QString addSubFolder( const QString& folderName );
130
131 /**
132 * Removes subfolder with the given @p folderName. Returns success or failure.
133 */
134 bool removeSubFolder( const QString& folderName );
135
136 /**
137 * Returns a Maildir object for the given @p folderName. If such a folder
138 * exists, the Maildir object will be valid, otherwise you can call create()
139 * on it, to make a subfolder with that name.
140 */
141 Maildir subFolder( const QString& folderName ) const;
142
143 /**
144 * Returns the parent Maildir object for this Maildir, if there is one (ie. this is not the root).
145 */
146 Maildir parent() const;
147
148 /**
149 * Returns the size of the file in the maildir with the given @p key or \c -1 if key is not valid.
150 * @since 4.2
151 */
152 qint64 size( const QString& key ) const;
153
154 /**
155 * Returns the modification time of the file in the maildir with the given @p key.
156 * @since 4.7
157 */
158 QDateTime lastModified( const QString &key ) const;
159
160 /**
161 * Return the contents of the file in the maildir with the given @p key.
162 */
163 QByteArray readEntry( const QString& key ) const;
164
165 enum MailFlags {
166 Forwarded,
167 Replied,
168 Seen,
169 Flagged
170 };
171 Q_DECLARE_FLAGS(Flags, MailFlags);
172
173 /**
174 * Return the flags encoded in the maildir file name for an entry
175 **/
176 Flags readEntryFlags( const QString& key ) const;
177
178 /**
179 * Return the contents of the headers section of the file the maildir with the given @p file, that
180 * is a full path to the file. You can get it by using findRealKey(key) .
181 */
182 QByteArray readEntryHeadersFromFile( const QString& file ) const;
183
184 /**
185 * Return the contents of the headers section of the file the maildir with the given @p key.
186 */
187 QByteArray readEntryHeaders( const QString& key ) const;
188
189 /**
190 * Write the given @p data to a file in the maildir with the given @p key.
191 * Returns true in case of success, false in case of any error.
192 */
193 bool writeEntry( const QString& key, const QByteArray& data );
194
195 /**
196 * Adds the given @p data to the maildir. Returns the key of the entry.
197 */
198 QString addEntry( const QByteArray& data );
199
200 /**
201 * Removes the entry with the given @p key. Returns success or failure.
202 */
203 bool removeEntry( const QString& key );
204
205 /**
206 * Change the flags for an entry specified by @p key. Returns the new key of the entry (the key might change because
207 * flags are stored in the unique filename).
208 */
209 // QString changeEntryFlags( const QString& key, const Akonadi::Item::Flags& flags );
210
211 /**
212 * Moves this maildir into @p destination.
213 */
214 bool moveTo( const Maildir &destination );
215
216 /**
217 * Renames this maildir to @p newName.
218 */
219 bool rename( const QString &newName );
220
221 /**
222 * Moves the file with the given @p key into the Maildir @p destination.
223 * @returns The new file name inside @p destination.
224 */
225 QString moveEntryTo( const QString& key, const KPIM::Maildir& destination );
226
227 /**
228 * Creates the maildir tree structure specific directory path that the
229 * given @p folderPath folder would have for its sub folders
230 * @param folderPath a maildir folder path
231 * @return the relative subDirPath for the given @p folderPath
232 *
233 * @see subDirNameForFolderName()
234 */
235 static QString subDirPathForFolderPath( const QString &folderPath );
236
237 /**
238 * Creates the maildir tree structure specific directory name that the
239 * given @p folderName folder would have for its sub folders
240 * @param folderName a maildir folder name
241 * @return the relative subDirName for the given @p folderMame
242 *
243 * @see subDirPathForFolderPath()
244 */
245 static QString subDirNameForFolderName( const QString &folderName );
246
247 /** Removes the listed keys from the key cache */
248 void removeCachedKeys(const QStringList & keys);
249
250
251 /** Reloads the keys associated with the maildir in the key cache*/
252 void refreshKeyCache();
253
254 /** Return the last error message string. The error might not come from the last performed operation,
255 if that was sucessful. The caller should always check the return value of the methods before
256 querying the last error string. */
257 QString lastError() const;
258
259private:
260 void swap( const Maildir& );
261 class Private;
262 Private *d;
263};
264
265}
266#endif // __MAILDIR_H__
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
new file mode 100644
index 0000000..f9cc2a4
--- /dev/null
+++ b/examples/maildirresource/maildirresource.cpp
@@ -0,0 +1,284 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "maildirresource.h"
21#include "facade.h"
22#include "entitybuffer.h"
23#include "pipeline.h"
24#include "mail_generated.h"
25#include "createentity_generated.h"
26#include "domainadaptor.h"
27#include "resourceconfig.h"
28#include "commands.h"
29#include "index.h"
30#include "log.h"
31#include "domain/mail.h"
32#include "definitions.h"
33#include "facadefactory.h"
34#include "indexupdater.h"
35#include "libmaildir/maildir.h"
36#include <QDate>
37#include <QUuid>
38#include <QDir>
39#include <QDirIterator>
40#include <KMime/KMime/KMimeMessage>
41
42//This is the resources entity type, and not the domain type
43#define ENTITY_TYPE_MAIL "mail"
44#define ENTITY_TYPE_FOLDER "folder"
45
46MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline)
47 : Akonadi2::GenericResource(instanceIdentifier, pipeline)
48{
49 addType(ENTITY_TYPE_MAIL, QSharedPointer<MaildirMailAdaptorFactory>::create(),
50 QVector<Akonadi2::Preprocessor*>() << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Mail>);
51 addType(ENTITY_TYPE_MAIL, QSharedPointer<MaildirMailAdaptorFactory>::create(),
52 QVector<Akonadi2::Preprocessor*>() << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Folder>);
53 auto config = ResourceConfig::getConfiguration(instanceIdentifier);
54 mMaildirPath = config.value("path").toString();
55}
56
57QString MaildirResource::resolveRemoteId(const QByteArray &bufferType, const QString &remoteId, Akonadi2::Storage::Transaction &transaction)
58{
59 //Lookup local id for remote id, or insert a new pair otherwise
60 auto remoteIdWithType = bufferType + remoteId.toUtf8();
61 QByteArray akonadiId = Index("rid.mapping", transaction).lookup(remoteIdWithType);
62 if (akonadiId.isEmpty()) {
63 akonadiId = QUuid::createUuid().toString().toUtf8();
64 Index("rid.mapping", transaction).add(remoteIdWithType, akonadiId);
65 }
66 return akonadiId;
67}
68
69static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir )
70{
71 QStringList list;
72 foreach (const QString &sub, dir.subFolderList()) {
73 const KPIM::Maildir md = dir.subFolder(sub);
74 if (!md.isValid()) {
75 continue;
76 }
77 QString path = root + QDir::separator() + sub;
78 list << path;
79 list += listRecursive(path, md );
80 }
81 return list;
82}
83
84QStringList MaildirResource::listAvailableFolders()
85{
86 KPIM::Maildir dir(mMaildirPath, true);
87 if (!dir.isValid()) {
88 return QStringList();
89 }
90 QStringList folderList;
91 folderList << mMaildirPath;
92 folderList += listRecursive(mMaildirPath, dir);
93 return folderList;
94}
95
96void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction)
97{
98 const QString bufferType = ENTITY_TYPE_FOLDER;
99 QStringList folderList = listAvailableFolders();
100 Trace() << "Found folders " << folderList;
101
102 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
103 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
104 Index ridMapping("rid.mapping", synchronizationTransaction);
105 for (const auto folder : folderList) {
106 const auto remoteId = folder.toUtf8();
107 auto akonadiId = resolveRemoteId(bufferType.toUtf8(), remoteId, synchronizationTransaction);
108
109 bool found = false;
110 transaction.openDatabase(bufferType.toUtf8() + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool {
111 found = true;
112 return false;
113 }, [this](const Akonadi2::Storage::Error &error) {
114 }, true);
115
116 if (!found) { //A new entity
117 m_fbb.Clear();
118
119 KPIM::Maildir md(folder);
120
121 flatbuffers::FlatBufferBuilder entityFbb;
122 auto name = m_fbb.CreateString(md.name().toStdString());
123 auto icon = m_fbb.CreateString("folder");
124 flatbuffers::Offset<flatbuffers::String> parent;
125
126 if (!md.isRoot()) {
127 auto akonadiId = resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), transaction);
128 parent = m_fbb.CreateString(akonadiId.toStdString());
129 }
130
131 auto builder = Akonadi2::ApplicationDomain::Buffer::FolderBuilder(m_fbb);
132 builder.add_name(name);
133 if (!md.isRoot()) {
134 builder.add_parent(parent);
135 }
136 builder.add_icon(icon);
137 auto buffer = builder.Finish();
138 Akonadi2::ApplicationDomain::Buffer::FinishFolderBuffer(m_fbb, buffer);
139 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
140
141 flatbuffers::FlatBufferBuilder fbb;
142 //This is the resource type and not the domain type
143 auto entityId = fbb.CreateString(akonadiId.toStdString());
144 auto type = fbb.CreateString(bufferType.toStdString());
145 auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize());
146 auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta);
147 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location);
148
149 Trace() << "Found a new entity: " << remoteId;
150 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()));
151 } else { //modification
152 Trace() << "Found a modified entity: " << remoteId;
153 //TODO diff and create modification if necessary
154 }
155 }
156 //TODO find items to remove
157}
158
159void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transaction, const QString &path)
160{
161 Trace() << "Synchronizing mails" << path;
162 const QString bufferType = ENTITY_TYPE_MAIL;
163
164 KPIM::Maildir maildir(path, true);
165 if (!maildir.isValid()) {
166 Warning() << "Failed to sync folder " << maildir.lastError();
167 return;
168 }
169
170 auto listingPath = maildir.pathToCurrent();
171 auto entryIterator = QSharedPointer<QDirIterator>::create(listingPath, QDir::Files);
172 Trace() << "Looking into " << maildir.pathToNew();
173
174 QFileInfo entryInfo;
175
176 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
177 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
178 Index ridMapping("rid.mapping", synchronizationTransaction);
179
180 while (entryIterator->hasNext()) {
181 QString filePath = entryIterator->next();
182 QString fileName = entryIterator->fileName();
183
184 const auto remoteId = fileName.toUtf8();
185 auto akonadiId = resolveRemoteId(bufferType.toUtf8(), remoteId, synchronizationTransaction);
186
187 bool found = false;
188 transaction.openDatabase(bufferType.toUtf8() + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool {
189 found = true;
190 return false;
191 }, [this](const Akonadi2::Storage::Error &error) {
192 }, true);
193
194 if (!found) { //A new entity
195 m_fbb.Clear();
196
197 KMime::Message *msg = new KMime::Message;
198 auto filepath = listingPath + QDir::separator() + fileName;
199 msg->setHead(KMime::CRLFtoLF(maildir.readEntryHeadersFromFile(filepath)));
200 msg->parse();
201
202 const auto flags = maildir.readEntryFlags(fileName);
203
204 Trace() << "Found a mail " << filePath << fileName << msg->subject(true)->asUnicodeString();
205 flatbuffers::FlatBufferBuilder entityFbb;
206 auto subject = m_fbb.CreateString(msg->subject(true)->asUnicodeString().toStdString());
207 auto sender = m_fbb.CreateString(msg->from(true)->asUnicodeString().toStdString());
208 auto senderName = m_fbb.CreateString(msg->from(true)->asUnicodeString().toStdString());
209 auto date = m_fbb.CreateString(msg->date(true)->dateTime().toString().toStdString());
210 auto folder = m_fbb.CreateString(resolveRemoteId(ENTITY_TYPE_FOLDER, path, transaction).toStdString());
211 auto mimeMessage = m_fbb.CreateString(filepath.toStdString());
212
213 auto builder = Akonadi2::ApplicationDomain::Buffer::MailBuilder(m_fbb);
214 builder.add_subject(subject);
215 builder.add_sender(sender);
216 builder.add_senderName(senderName);
217 builder.add_unread(!(flags & KPIM::Maildir::Seen));
218 builder.add_important(flags & KPIM::Maildir::Flagged);
219 builder.add_date(date);
220 builder.add_folder(folder);
221 builder.add_mimeMessage(mimeMessage);
222 auto buffer = builder.Finish();
223 Akonadi2::ApplicationDomain::Buffer::FinishMailBuffer(m_fbb, buffer);
224 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
225
226 flatbuffers::FlatBufferBuilder fbb;
227 //This is the resource type and not the domain type
228 auto entityId = fbb.CreateString(akonadiId.toStdString());
229 auto type = fbb.CreateString(bufferType.toStdString());
230 auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize());
231 auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta);
232 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location);
233
234 Trace() << "Found a new entity: " << remoteId;
235 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()));
236 } else { //modification
237 Trace() << "Found a modified entity: " << remoteId;
238 //TODO diff and create modification if necessary
239 }
240 }
241 //TODO find items to remove
242}
243
244KAsync::Job<void> MaildirResource::synchronizeWithSource()
245{
246 Log() << " Synchronizing";
247 return KAsync::start<void>([this]() {
248 auto transaction = Akonadi2::Storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier, Akonadi2::Storage::ReadOnly).createTransaction(Akonadi2::Storage::ReadOnly);
249 synchronizeFolders(transaction);
250 for (const auto &folder : listAvailableFolders()) {
251 synchronizeMails(transaction, folder);
252 }
253 });
254}
255
256KAsync::Job<void> MaildirResource::replay(const QByteArray &type, const QByteArray &key, const QByteArray &value)
257{
258 Trace() << "Replaying " << key;
259 return KAsync::null<void>();
260}
261
262void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier)
263{
264 GenericResource::removeFromDisk(instanceIdentifier);
265 Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk();
266}
267
268MaildirResourceFactory::MaildirResourceFactory(QObject *parent)
269 : Akonadi2::ResourceFactory(parent)
270{
271
272}
273
274Akonadi2::Resource *MaildirResourceFactory::createResource(const QByteArray &instanceIdentifier)
275{
276 return new MaildirResource(instanceIdentifier);
277}
278
279void MaildirResourceFactory::registerFacades(Akonadi2::FacadeFactory &factory)
280{
281 factory.registerFacade<Akonadi2::ApplicationDomain::Mail, MaildirResourceMailFacade>(PLUGIN_NAME);
282 factory.registerFacade<Akonadi2::ApplicationDomain::Folder, MaildirResourceFolderFacade>(PLUGIN_NAME);
283}
284
diff --git a/examples/maildirresource/maildirresource.h b/examples/maildirresource/maildirresource.h
new file mode 100644
index 0000000..a3e12d3
--- /dev/null
+++ b/examples/maildirresource/maildirresource.h
@@ -0,0 +1,61 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#pragma once
21
22#include "common/genericresource.h"
23
24#include <Async/Async>
25
26#include <flatbuffers/flatbuffers.h>
27
28//TODO: a little ugly to have this in two places, once here and once in Q_PLUGIN_METADATA
29#define PLUGIN_NAME "org.kde.maildir"
30
31class MaildirResource : public Akonadi2::GenericResource
32{
33public:
34 MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline = QSharedPointer<Akonadi2::Pipeline>());
35 KAsync::Job<void> synchronizeWithSource() Q_DECL_OVERRIDE;
36 static void removeFromDisk(const QByteArray &instanceIdentifier);
37private:
38 KAsync::Job<void> replay(const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE;
39 QString resolveRemoteId(const QByteArray &type, const QString &remoteId, Akonadi2::Storage::Transaction &transaction);
40 // void createMail(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb, Akonadi2::Storage::Transaction &);
41 // void createFolder(const QByteArray &rid, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb, Akonadi2::Storage::Transaction &);
42 // void synchronize(const QString &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Akonadi2::Storage::Transaction &transaction, std::function<void(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, flatbuffers::FlatBufferBuilder &entityFbb, Akonadi2::Storage::Transaction &)> createEntity);
43 void synchronizeFolders(Akonadi2::Storage::Transaction &transaction);
44 void synchronizeMails(Akonadi2::Storage::Transaction &transaction, const QString &folder);
45 QStringList listAvailableFolders();
46 QString mMaildirPath;
47};
48
49class MaildirResourceFactory : public Akonadi2::ResourceFactory
50{
51 Q_OBJECT
52 Q_PLUGIN_METADATA(IID "org.kde.maildir")
53 Q_INTERFACES(Akonadi2::ResourceFactory)
54
55public:
56 MaildirResourceFactory(QObject *parent = 0);
57
58 Akonadi2::Resource *createResource(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE;
59 void registerFacades(Akonadi2::FacadeFactory &factory) Q_DECL_OVERRIDE;
60};
61
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 11fe415..b909681 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -47,8 +47,13 @@ auto_tests (
47 pipelinetest 47 pipelinetest
48 querytest 48 querytest
49) 49)
50
51target_link_libraries(dummyresourcetest akonadi2_resource_dummy) 50target_link_libraries(dummyresourcetest akonadi2_resource_dummy)
52target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) 51target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy)
53target_link_libraries(querytest akonadi2_resource_dummy) 52target_link_libraries(querytest akonadi2_resource_dummy)
54 53
54if (BUILD_MAILDIR)
55 auto_tests (
56 maildirresourcetest
57 )
58 target_link_libraries(maildirresourcetest akonadi2_resource_maildir)
59endif()
diff --git a/tests/maildirresourcetest.cpp b/tests/maildirresourcetest.cpp
new file mode 100644
index 0000000..c65cdf0
--- /dev/null
+++ b/tests/maildirresourcetest.cpp
@@ -0,0 +1,66 @@
1#include <QtTest>
2
3#include <QString>
4
5#include "maildirresource/maildirresource.h"
6#include "clientapi.h"
7#include "commands.h"
8#include "entitybuffer.h"
9#include "resourceconfig.h"
10#include "modelresult.h"
11#include "pipeline.h"
12#include "log.h"
13
14/**
15 * Test of complete system using the maildir resource.
16 *
17 * This test requires the maildir resource installed.
18 */
19class MaildirResourceTest : public QObject
20{
21 Q_OBJECT
22private Q_SLOTS:
23 void initTestCase()
24 {
25 Akonadi2::Log::setDebugOutputLevel(Akonadi2::Log::Trace);
26 auto factory = Akonadi2::ResourceFactory::load("org.kde.maildir");
27 QVERIFY(factory);
28 MaildirResource::removeFromDisk("org.kde.maildir.instance1");
29 Akonadi2::ApplicationDomain::AkonadiResource resource;
30 resource.setProperty("identifier", "org.kde.maildir.instance1");
31 resource.setProperty("type", "org.kde.maildir");
32 resource.setProperty("path", "/work/build/local-mail");
33 Akonadi2::Store::create(resource).exec().waitForFinished();
34 }
35
36 void cleanup()
37 {
38 Akonadi2::Store::shutdown(QByteArray("org.kde.maildir.instance1")).exec().waitForFinished();
39 MaildirResource::removeFromDisk("org.kde.maildir.instance1");
40 }
41
42 void init()
43 {
44 qDebug();
45 qDebug() << "-----------------------------------------";
46 qDebug();
47 }
48
49 void testListFolders()
50 {
51 Akonadi2::Query query;
52 query.resources << "org.kde.maildir.instance1";
53 query.syncOnDemand = true;
54 query.processAll = true;
55
56 //Ensure all local data is processed
57 Akonadi2::Store::synchronize(query).exec().waitForFinished();
58
59 auto model = Akonadi2::Store::loadModel<Akonadi2::ApplicationDomain::Folder>(query);
60 QTRY_VERIFY(model->rowCount(QModelIndex()) > 1);
61 }
62
63};
64
65QTEST_MAIN(MaildirResourceTest)
66#include "maildirresourcetest.moc"