diff options
Diffstat (limited to 'examples/maildirresource/libmaildir/maildir.cpp')
-rw-r--r-- | examples/maildirresource/libmaildir/maildir.cpp | 865 |
1 files changed, 865 insertions, 0 deletions
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 | } | ||