MailImporter

filterkmailarchive.cpp
1/*
2 SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5*/
6
7#include "filterkmailarchive.h"
8
9#include <KLocalizedString>
10#include <KTar>
11#include <KZip>
12#include <QFileDialog>
13
14#include "mailimporter_debug.h"
15#include <QApplication>
16
17#include <QSharedPointer>
18
19#include <QMimeDatabase>
20#include <QMimeType>
21
22using namespace MailImporter;
23
24class MailImporter::FilterKMailArchivePrivate
25{
26public:
27 int mTotalFiles = 0;
28 int mFilesDone = 0;
29};
30FilterKMailArchive::FilterKMailArchive()
31 : Filter(i18n("Import KMail Archive File"),
32 QStringLiteral("Klar\xE4lvdalens Datakonsult AB"),
33 i18n("<p><b>KMail Archive File Import Filter</b></p>"
34 "<p>This filter will import archives files previously exported by KMail.</p>"
35 "<p>Archive files contain a complete folder subtree compressed into a single file.</p>"))
36 , d(new MailImporter::FilterKMailArchivePrivate)
37{
38}
39
40FilterKMailArchive::~FilterKMailArchive() = default;
41
42// Input: .inbox.directory
43// Output: inbox
44// Can also return an empty string if this is no valid dir name
45static QString folderNameForDirectoryName(const QString &dirName)
46{
47 Q_ASSERT(dirName.startsWith(QLatin1Char('.')));
48 const QString end = QStringLiteral(".directory");
49 const int expectedIndex = dirName.length() - end.length();
50 if (dirName.toLower().indexOf(end) != expectedIndex) {
51 return QString();
52 }
53 QString returnName = dirName.left(dirName.length() - end.length());
54 returnName = returnName.right(returnName.length() - 1);
55 return returnName;
56}
57
58bool FilterKMailArchive::importMessage(const KArchiveFile *file, const QString &folderPath)
59{
60 if (filterInfo()->shouldTerminate()) {
61 return false;
62 }
63
64 qApp->processEvents();
65 return filterImporter()->importMessage(file, folderPath, d->mTotalFiles, d->mFilesDone);
66}
67
68bool FilterKMailArchive::importFolder(const KArchiveDirectory *folder, const QString &folderPath)
69{
70 qCDebug(MAILIMPORTER_LOG) << "Importing folder" << folder->name();
71 filterInfo()->addInfoLogEntry(i18n("Importing folder '%1'...", folderPath));
72 filterInfo()->setTo(filterImporter()->topLevelFolder() + folderPath);
73 const KArchiveDirectory *const messageDir = dynamic_cast<const KArchiveDirectory *>(folder->entry(QStringLiteral("cur")));
74 if (messageDir) {
75 int total = messageDir->entries().count();
76 int cur = 1;
77
78 const QStringList lstEntries = messageDir->entries();
79 for (const QString &entryName : lstEntries) {
80 filterInfo()->setCurrent(cur * 100 / total);
81 filterInfo()->setOverall(d->mTotalFiles == 0 ? 0 : (d->mFilesDone * 100 / d->mTotalFiles));
82 const KArchiveEntry *const entry = messageDir->entry(entryName);
83
84 if (entry->isFile()) {
85 const int oldCount = d->mFilesDone;
86 if (!importMessage(static_cast<const KArchiveFile *>(entry), folderPath)) {
87 return false;
88 }
89
90 // Adjust the counter. Total count can decrease because importMessage() detects a duplicate
91 if (oldCount != d->mFilesDone) {
92 cur++;
93 } else {
94 total--;
95 }
96 } else {
97 filterInfo()->addErrorLogEntry(i18n("Unexpected subfolder %1 in folder %2.", entryName, folder->name()));
98 }
99 }
100 } else {
101 filterInfo()->addErrorLogEntry(i18n("No subfolder named 'cur' in folder %1.", folder->name()));
102 }
103 return true;
104}
105
106bool FilterKMailArchive::importDirectory(const KArchiveDirectory *directory, const QString &folderPath)
107{
108 qCDebug(MAILIMPORTER_LOG) << "Importing directory" << directory->name();
109 const QStringList lstEntries = directory->entries();
110 for (const QString &entryName : lstEntries) {
111 const KArchiveEntry *const entry = directory->entry(entryName);
112
113 if (entry->isDirectory()) {
114 const auto dir = static_cast<const KArchiveDirectory *>(entry);
115
116 if (!dir->name().startsWith(QLatin1Char('.'))) {
117 if (!importFolder(dir, folderPath + QLatin1Char('/') + dir->name())) {
118 return false;
119 }
120 }
121 // Entry starts with a dot, so we assume it is a subdirectory
122 else {
123 const QString folderName = folderNameForDirectoryName(entry->name());
124 if (folderName.isEmpty()) {
125 filterInfo()->addErrorLogEntry(i18n("Unexpected subdirectory named '%1'.", entry->name()));
126 } else {
127 if (!importDirectory(dir, folderPath + QLatin1Char('/') + folderName)) {
128 return false;
129 }
130 }
131 }
132 }
133 }
134
135 return true;
136}
137
138int FilterKMailArchive::countFiles(const KArchiveDirectory *directory) const
139{
140 int count = 0;
141 const QStringList lstEntries = directory->entries();
142 for (const QString &entryName : lstEntries) {
143 const KArchiveEntry *const entry = directory->entry(entryName);
144 if (entry->isFile()) {
145 count++;
146 } else {
147 count += countFiles(static_cast<const KArchiveDirectory *>(entry));
148 }
149 }
150 return count;
151}
152
153void FilterKMailArchive::import()
154{
155 const QString archiveFile = QFileDialog::getOpenFileName(filterInfo()->parentWidget(),
156 i18n("Select KMail Archive File to Import"),
157 QString(),
158 QStringLiteral("%1 (*.tar *.tar.gz *.tar.bz2 *.zip)").arg(i18n("KMail Archive Files ")));
159 if (archiveFile.isEmpty()) {
160 filterInfo()->alert(i18n("Please select an archive file that should be imported."));
161 return;
162 }
163 importMails(archiveFile);
164}
165
166void FilterKMailArchive::importMails(const QString &archiveFile)
167{
168 if (archiveFile.isEmpty()) {
169 filterInfo()->alert(i18n("No archive selected."));
170 return;
171 }
172 filterInfo()->setFrom(archiveFile);
173
174 QMimeDatabase db;
176 using KArchivePtr = QSharedPointer<KArchive>;
177 KArchivePtr archive;
178 if (!mimeType.globPatterns().filter(QStringLiteral("tar"), Qt::CaseInsensitive).isEmpty()) {
179 archive = KArchivePtr(new KTar(archiveFile));
180 } else if (!mimeType.globPatterns().filter(QStringLiteral("zip"), Qt::CaseInsensitive).isEmpty()) {
181 archive = KArchivePtr(new KZip(archiveFile));
182 } else {
183 filterInfo()->alert(i18n("The file '%1' does not appear to be a valid archive.", archiveFile));
184 return;
185 }
186
187 if (!archive->open(QIODevice::ReadOnly)) {
188 filterInfo()->alert(i18n("Unable to open archive file '%1'", archiveFile));
189 return;
190 }
191
192 filterInfo()->setOverall(0);
193 filterInfo()->addInfoLogEntry(i18n("Counting files in archive..."));
194 d->mTotalFiles = countFiles(archive->directory());
195
196 if (importDirectory(archive->directory(), QString())) {
197 filterInfo()->setOverall(100);
198 filterInfo()->setCurrent(100);
199 filterInfo()->addInfoLogEntry(i18n("Importing the archive file '%1' into the folder '%2' succeeded.", archiveFile, filterImporter()->topLevelFolder()));
200 filterInfo()->addInfoLogEntry(i18np("1 message was imported.", "%1 messages were imported.", d->mFilesDone));
201 } else {
202 filterInfo()->addInfoLogEntry(i18n("Importing the archive failed."));
203 }
204 archive->close();
205}
QStringList entries() const
const KArchiveEntry * entry(const QString &name) const
virtual bool isDirectory() const
QString name() const
virtual bool isFile() const
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT QString dir(const QString &fileClass)
const QList< QKeySequence > & end()
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
qsizetype count() const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString right(qsizetype n) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
CaseInsensitive
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:39 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.