diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-15 17:05:20 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-15 17:05:20 +0100 |
commit | 1710fab0965d32b883dfcc327c36d3fd38a91357 (patch) | |
tree | 1ba0d5d4728ff6405846a99549293a4b4c69da25 | |
parent | 62d74778fa315091c5f0b99093bd806a1ccd7a79 (diff) | |
download | sink-1710fab0965d32b883dfcc327c36d3fd38a91357.tar.gz sink-1710fab0965d32b883dfcc327c36d3fd38a91357.zip |
A read-only maildir resource.
Respectively a first prototype thereof.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | common/domain/mail.fbs | 1 | ||||
-rw-r--r-- | examples/CMakeLists.txt | 5 | ||||
-rw-r--r-- | examples/maildirresource/CMakeLists.txt | 15 | ||||
-rw-r--r-- | examples/maildirresource/domainadaptor.cpp | 35 | ||||
-rw-r--r-- | examples/maildirresource/domainadaptor.h | 38 | ||||
-rw-r--r-- | examples/maildirresource/facade.cpp | 41 | ||||
-rw-r--r-- | examples/maildirresource/facade.h | 36 | ||||
-rw-r--r-- | examples/maildirresource/libmaildir/CMakeLists.txt | 8 | ||||
-rw-r--r-- | examples/maildirresource/libmaildir/keycache.cpp | 88 | ||||
-rw-r--r-- | examples/maildirresource/libmaildir/keycache.h | 77 | ||||
-rw-r--r-- | examples/maildirresource/libmaildir/maildir.cpp | 865 | ||||
-rw-r--r-- | examples/maildirresource/libmaildir/maildir.h | 266 | ||||
-rw-r--r-- | examples/maildirresource/maildirresource.cpp | 284 | ||||
-rw-r--r-- | examples/maildirresource/maildirresource.h | 61 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/maildirresourcetest.cpp | 66 |
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 @@ | |||
1 | cmake_minimum_required(VERSION 2.8.12) | 1 | cmake_minimum_required(VERSION 2.8.12) |
2 | 2 | ||
3 | option(BUILD_MAILDIR "BUILD_MAILDIR" ON) | ||
3 | 4 | ||
4 | # ECM setup | 5 | # ECM setup |
5 | find_package(ECM 0.0.10 REQUIRED NO_MODULE) | 6 | find_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 | ||
14 | root_type Mail; | 15 | root_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 |
5 | add_subdirectory(dummyresource) | 5 | add_subdirectory(dummyresource) |
6 | |||
7 | if (BUILD_MAILDIR) | ||
8 | # a maildir resource implementation | ||
9 | add_subdirectory(maildirresource) | ||
10 | endif() | ||
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 @@ | |||
1 | project(akonadi2_resource_maildir) | ||
2 | |||
3 | add_definitions(-DQT_PLUGIN) | ||
4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | ||
5 | |||
6 | find_package(KF5 COMPONENTS REQUIRED Mime) | ||
7 | |||
8 | add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) | ||
9 | # generate_flatbuffers(${PROJECT_NAME} dummycalendar) | ||
10 | qt5_use_modules(${PROJECT_NAME} Core Network) | ||
11 | target_link_libraries(${PROJECT_NAME} akonadi2common maildir KF5::Mime) | ||
12 | |||
13 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${AKONADI2_RESOURCE_PLUGINS_PATH}) | ||
14 | |||
15 | add_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 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | MaildirMailAdaptorFactory::MaildirMailAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | MaildirFolderAdaptorFactory::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 | |||
26 | class MaildirMailAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Mail, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | MaildirMailAdaptorFactory(); | ||
30 | virtual ~MaildirMailAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class MaildirFolderAdaptorFactory : public DomainTypeAdaptorFactory<Akonadi2::ApplicationDomain::Folder, Akonadi2::ApplicationDomain::Buffer::Dummy, Akonadi2::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
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 | |||
24 | MaildirResourceMailFacade::MaildirResourceMailFacade(const QByteArray &instanceIdentifier) | ||
25 | : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<MaildirMailAdaptorFactory>::create()) | ||
26 | { | ||
27 | } | ||
28 | |||
29 | MaildirResourceMailFacade::~MaildirResourceMailFacade() | ||
30 | { | ||
31 | } | ||
32 | |||
33 | |||
34 | MaildirResourceFolderFacade::MaildirResourceFolderFacade(const QByteArray &instanceIdentifier) | ||
35 | : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<MaildirFolderAdaptorFactory>::create()) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | MaildirResourceFolderFacade::~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 | |||
24 | class MaildirResourceMailFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Mail> | ||
25 | { | ||
26 | public: | ||
27 | MaildirResourceMailFacade(const QByteArray &instanceIdentifier); | ||
28 | virtual ~MaildirResourceMailFacade(); | ||
29 | }; | ||
30 | |||
31 | class MaildirResourceFolderFacade : public Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Folder> | ||
32 | { | ||
33 | public: | ||
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 | |||
3 | set(maildir_LIB_SRCS keycache.cpp maildir.cpp) | ||
4 | |||
5 | add_library(maildir ${LIBRARY_TYPE} ${maildir_LIB_SRCS}) | ||
6 | qt5_use_modules(maildir Core Network) | ||
7 | # set_target_properties(maildir PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) | ||
8 | 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 @@ | |||
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 | |||
24 | KeyCache* KeyCache::mSelf = 0; | ||
25 | |||
26 | void 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 | |||
39 | void KeyCache::refreshKeys( const QString& dir ) | ||
40 | { | ||
41 | mNewKeys.remove( dir ); | ||
42 | mCurKeys.remove( dir ); | ||
43 | addKeys( dir ); | ||
44 | } | ||
45 | |||
46 | void 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 | |||
52 | void 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 | |||
58 | void 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 | |||
65 | bool KeyCache::isCurKey( const QString& dir, const QString& key ) const | ||
66 | { | ||
67 | return mCurKeys.value( dir ).contains( key ); | ||
68 | } | ||
69 | |||
70 | bool KeyCache::isNewKey( const QString& dir, const QString& key ) const | ||
71 | { | ||
72 | return mNewKeys.value( dir ).contains( key ); | ||
73 | } | ||
74 | |||
75 | QSet< 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 | |||
82 | QSet< 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 | |||
30 | class KeyCache { | ||
31 | |||
32 | public: | ||
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 | |||
61 | private: | ||
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 | |||
57 | bool 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 | |||
83 | using namespace KPIM; | ||
84 | |||
85 | Q_GLOBAL_STATIC_WITH_ARGS(QRegExp, statusSeparatorRx, (":|!")) | ||
86 | |||
87 | class Maildir::Private | ||
88 | { | ||
89 | public: | ||
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 | |||
220 | Maildir::Maildir(const QString& path, bool isRoot) | ||
221 | :d(new Private(path, isRoot)) | ||
222 | { | ||
223 | } | ||
224 | |||
225 | void Maildir::swap(const Maildir &rhs) | ||
226 | { | ||
227 | Private *p = d; | ||
228 | d = new Private(*rhs.d); | ||
229 | delete p; | ||
230 | } | ||
231 | |||
232 | |||
233 | Maildir::Maildir(const Maildir & rhs) | ||
234 | :d(new Private(*rhs.d)) | ||
235 | |||
236 | { | ||
237 | } | ||
238 | |||
239 | Maildir& 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 | |||
248 | bool Maildir::operator== (const Maildir & rhs) const | ||
249 | { | ||
250 | return *d == *rhs.d; | ||
251 | } | ||
252 | |||
253 | |||
254 | Maildir::~Maildir() | ||
255 | { | ||
256 | delete d; | ||
257 | } | ||
258 | |||
259 | bool 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 | |||
267 | bool 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 | |||
294 | bool 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 | |||
316 | bool Maildir::isRoot() const | ||
317 | { | ||
318 | return d->isRoot; | ||
319 | } | ||
320 | |||
321 | bool 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 | |||
335 | QString Maildir::path() const | ||
336 | { | ||
337 | return d->path; | ||
338 | } | ||
339 | |||
340 | QString Maildir::name() const | ||
341 | { | ||
342 | const QDir dir(d->path); | ||
343 | return dir.dirName(); | ||
344 | } | ||
345 | |||
346 | QString 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 | |||
367 | bool 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 | |||
386 | Maildir 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 | |||
399 | Maildir 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 | |||
413 | QStringList 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 | |||
424 | QStringList Maildir::listCurrent() const | ||
425 | { | ||
426 | QStringList result; | ||
427 | if (isValid()) { | ||
428 | result += d->listCurrent(); | ||
429 | } | ||
430 | return result; | ||
431 | } | ||
432 | |||
433 | QString Maildir::findRealKey(const QString& key) const | ||
434 | { | ||
435 | return d->findRealKey(key); | ||
436 | } | ||
437 | |||
438 | |||
439 | QStringList Maildir::listNew() const | ||
440 | { | ||
441 | QStringList result; | ||
442 | if (isValid()) { | ||
443 | result += d->listNew(); | ||
444 | } | ||
445 | return result; | ||
446 | } | ||
447 | |||
448 | QString Maildir::pathToNew() const | ||
449 | { | ||
450 | if (isValid()) { | ||
451 | return d->path + QString::fromLatin1("/new"); | ||
452 | } | ||
453 | return QString(); | ||
454 | } | ||
455 | |||
456 | QString Maildir::pathToCurrent() const | ||
457 | { | ||
458 | if (isValid()) { | ||
459 | return d->path + QString::fromLatin1("/cur"); | ||
460 | } | ||
461 | return QString(); | ||
462 | } | ||
463 | |||
464 | QString Maildir::subDirPath() const | ||
465 | { | ||
466 | QDir dir(d->path); | ||
467 | dir.cdUp(); | ||
468 | return dir.path() + QDir::separator() + d->subDirPath(); | ||
469 | } | ||
470 | |||
471 | |||
472 | |||
473 | QStringList 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 | |||
492 | QByteArray 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 | } | ||
515 | qint64 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 | |||
534 | QDateTime 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 | |||
550 | QByteArray 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 | |||
571 | QByteArray 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 | |||
584 | static 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 | |||
595 | bool 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 | |||
615 | QString 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 | |||
659 | bool 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 | // | ||
753 | Maildir::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 | |||
776 | bool 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 | |||
798 | bool 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 | |||
811 | QString 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 | |||
836 | QString 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 | |||
844 | QString Maildir::subDirNameForFolderName(const QString &folderName) | ||
845 | { | ||
846 | return Private::subDirNameForFolderName(folderName); | ||
847 | } | ||
848 | |||
849 | void 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 | |||
857 | void Maildir::refreshKeyCache() | ||
858 | { | ||
859 | KeyCache::self()->refreshKeys(d->path); | ||
860 | } | ||
861 | |||
862 | QString 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 | |||
29 | class QDateTime; | ||
30 | |||
31 | namespace KPIM { | ||
32 | |||
33 | class MAILDIR_EXPORT Maildir | ||
34 | { | ||
35 | public: | ||
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 | |||
259 | private: | ||
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 | |||
46 | MaildirResource::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 | |||
57 | QString 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 | |||
69 | static 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 | |||
84 | QStringList 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 | |||
96 | void 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 | |||
159 | void 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 | |||
244 | KAsync::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 | |||
256 | KAsync::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 | |||
262 | void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier) | ||
263 | { | ||
264 | GenericResource::removeFromDisk(instanceIdentifier); | ||
265 | Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk(); | ||
266 | } | ||
267 | |||
268 | MaildirResourceFactory::MaildirResourceFactory(QObject *parent) | ||
269 | : Akonadi2::ResourceFactory(parent) | ||
270 | { | ||
271 | |||
272 | } | ||
273 | |||
274 | Akonadi2::Resource *MaildirResourceFactory::createResource(const QByteArray &instanceIdentifier) | ||
275 | { | ||
276 | return new MaildirResource(instanceIdentifier); | ||
277 | } | ||
278 | |||
279 | void 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 | |||
31 | class MaildirResource : public Akonadi2::GenericResource | ||
32 | { | ||
33 | public: | ||
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); | ||
37 | private: | ||
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 | |||
49 | class MaildirResourceFactory : public Akonadi2::ResourceFactory | ||
50 | { | ||
51 | Q_OBJECT | ||
52 | Q_PLUGIN_METADATA(IID "org.kde.maildir") | ||
53 | Q_INTERFACES(Akonadi2::ResourceFactory) | ||
54 | |||
55 | public: | ||
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 | |||
51 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) | 50 | target_link_libraries(dummyresourcetest akonadi2_resource_dummy) |
52 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) | 51 | target_link_libraries(dummyresourcebenchmark akonadi2_resource_dummy) |
53 | target_link_libraries(querytest akonadi2_resource_dummy) | 52 | target_link_libraries(querytest akonadi2_resource_dummy) |
54 | 53 | ||
54 | if (BUILD_MAILDIR) | ||
55 | auto_tests ( | ||
56 | maildirresourcetest | ||
57 | ) | ||
58 | target_link_libraries(maildirresourcetest akonadi2_resource_maildir) | ||
59 | endif() | ||
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 | */ | ||
19 | class MaildirResourceTest : public QObject | ||
20 | { | ||
21 | Q_OBJECT | ||
22 | private 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 | |||
65 | QTEST_MAIN(MaildirResourceTest) | ||
66 | #include "maildirresourcetest.moc" | ||