MailImporter

filterthunderbird.cpp
1/*
2 filterthunderbird.cpp - Thunderbird mail import
3
4 SPDX-FileCopyrightText: 2005 Danny Kukawka <danny.kukawka@web.de>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "filterthunderbird.h"
10#include "selectthunderbirdprofilewidget.h"
11#include <KConfig>
12#include <KConfigGroup>
13#include <KLocalizedString>
14#include <QFile>
15#include <QFileDialog>
16#include <QPointer>
17#include <QRegularExpression>
18#include <QTemporaryFile>
19
20using namespace MailImporter;
21
22/** Default constructor. */
24 : Filter(i18n("Import Thunderbird/Mozilla Local Mails and Folder Structure"),
25 QStringLiteral("Danny Kukawka"),
26 i18n("<p><b>Thunderbird/Mozilla import filter</b></p>"
27 "<p>Select your base Thunderbird/Mozilla mailfolder"
28 " (usually ~/.thunderbird/*.default/Mail/Local Folders/).</p>"
29 "<p><b>Note:</b> Never choose a Folder which <u>does not</u> contain mbox-files (for example,"
30 " a maildir): if you do, you will get many new folders.</p>"
31 "<p>Since it is possible to recreate the folder structure, the folders "
32 "will be stored under: \"Thunderbird-Import\".</p>"))
33{
34}
35
36/** Destructor. */
40
41QString FilterThunderbird::isMailerFound()
42{
43 QDir directory(FilterThunderbird::defaultSettingsPath());
44 if (directory.exists()) {
45 QString currentProfile;
46 const QMap<QString, QString> listOfPath = FilterThunderbird::listProfile(currentProfile, FilterThunderbird::defaultSettingsPath());
47 bool foundMailConfigurated = false;
49 while (i != listOfPath.constEnd()) {
50 QDir dir(FilterThunderbird::defaultSettingsPath() + QLatin1Char('/') + i.value());
51 if (!dir.entryList(QStringList({QStringLiteral("ImapMail"), QStringLiteral("Mail")}), QDir::Dirs).isEmpty()) {
52 foundMailConfigurated = true;
53 break;
54 }
55 ++i;
56 }
57 if (foundMailConfigurated) {
58 return i18nc("name of thunderbird application", "Thunderbird");
59 } else {
60 return {};
61 }
62 }
63 return {};
64}
65
66QString FilterThunderbird::defaultSettingsPath()
67{
68 return QDir::homePath() + QLatin1StringView("/.thunderbird/");
69}
70
71QString FilterThunderbird::defaultProfile(const QString &defaultSettingPath, QWidget *parent)
72{
73 QString currentProfile;
74 QMap<QString, QString> listProfile = FilterThunderbird::listProfile(currentProfile, defaultSettingPath);
75 if (listProfile.isEmpty()) {
76 return QString();
77 } else if (listProfile.count() == 1) {
78 return currentProfile;
79 } else {
80 QPointer<SelectThunderbirdProfileDialog> dialog = new SelectThunderbirdProfileDialog(parent);
81 dialog->fillProfile(listProfile, currentProfile);
82 if (dialog->exec()) {
83 currentProfile = dialog->selectedProfile();
84 delete dialog;
85 return currentProfile;
86 }
87 delete dialog;
88 }
89 return currentProfile;
90}
91
92QMap<QString, QString> FilterThunderbird::listProfile(QString &currentProfile, const QString &defaultSettingPath)
93{
94 const QString thunderbirdPath = defaultSettingPath + QLatin1StringView("/profiles.ini");
95 QMap<QString, QString> lstProfile;
96 QFile profiles(thunderbirdPath);
97 if (profiles.exists()) {
98 // ini file.
99 KConfig config(thunderbirdPath);
100 const QStringList profileList = config.groupList().filter(QRegularExpression(QStringLiteral("Profile\\d+")));
101 const bool uniqProfile = (profileList.count() == 1);
102 if (uniqProfile) {
103 KConfigGroup group = config.group(profileList.at(0));
104 const QString path = group.readEntry("Path");
105 const QString name = group.readEntry(QStringLiteral("Name"));
106 currentProfile = path;
107 lstProfile.insert(name, path);
108 return lstProfile;
109 } else {
110 for (const QString &profileName : profileList) {
111 KConfigGroup group = config.group(profileName);
112 const QString path = group.readEntry("Path");
113 const QString name = group.readEntry(QStringLiteral("Name"));
114 if (group.hasKey("Default") && (group.readEntry("Default", 0) == 1)) {
115 currentProfile = path;
116 }
117 lstProfile.insert(name, path);
118 }
119 }
120 }
121 return lstProfile;
122}
123
124QString FilterThunderbird::defaultInstallFolder() const
125{
126 return QStringLiteral("Thunderbird-Import/");
127}
128
129QString FilterThunderbird::settingsPath()
130{
131 return FilterThunderbird::defaultSettingsPath();
132}
133
135{
136 /**
137 * We ask the user to choose Thunderbird's root directory.
138 * This should be usually ~/.thunderbird/xxxx.default/Mail/Local Folders/
139 */
140 QString thunderDir = settingsPath();
141 QDir d(thunderDir);
142 if (!d.exists()) {
143 thunderDir = QDir::homePath();
144 }
145 // Select directory from where I have to import files
146 const QString maildir = QFileDialog::getExistingDirectory(nullptr, QString(), thunderDir);
147 if (!maildir.isEmpty()) {
148 const QString mailDirThunderbird = maildir + QLatin1StringView("/Mail/Local Folders/");
149 if (QDir(mailDirThunderbird).exists()) {
150 importMails(mailDirThunderbird);
151 } else {
152 importMails(maildir);
153 }
154 }
155}
156
157bool FilterThunderbird::excludeFiles(const QString &file)
158{
159 if ((file.endsWith(QLatin1StringView(".msf")) || file.endsWith(QLatin1StringView(".dat")) || file.endsWith(QLatin1StringView(".json"))
160 || file.endsWith(QLatin1StringView(".html")))) {
161 return true;
162 }
163 return false;
164}
165
167{
168 if (maildir.isEmpty()) {
169 filterInfo()->alert(i18n("No directory selected."));
170 return;
171 }
172 setMailDir(maildir);
173 /**
174 * If the user only select homedir no import needed because
175 * there should be no files and we surely import wrong files.
176 */
177 if (mailDir() == QDir::homePath() || mailDir() == (QDir::homePath() + QLatin1Char('/'))) {
178 filterInfo()->addErrorLogEntry(i18n("No files found for import."));
179 } else {
180 filterInfo()->setOverall(0);
181 /** Recursive import of the MailArchives */
182 QDir dir(mailDir());
183 const QStringList rootSubDirs = dir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Dirs, QDir::Name); // Removal of . and ..
184 int currentDir = 1;
185 int numSubDirs = rootSubDirs.size();
186 QStringList::ConstIterator end = rootSubDirs.constEnd();
187 for (QStringList::ConstIterator filename = rootSubDirs.constBegin(); filename != end; ++filename, ++currentDir) {
188 if (filterInfo()->shouldTerminate()) {
189 break;
190 }
191 importDirContents(dir.filePath(*filename), *filename, *filename);
192 filterInfo()->setOverall((int)((float)currentDir / numSubDirs * 100));
193 }
194
195 /** import last but not least all archives from the root-dir */
196 QDir importDir(mailDir());
197 const QStringList files = importDir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Files, QDir::Name);
198 QStringList::ConstIterator mailFileEnd = files.constEnd();
199 for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != mailFileEnd; ++mailFile) {
200 if (filterInfo()->shouldTerminate()) {
201 break;
202 }
203 QString temp_mailfile = *mailFile;
204 if (!excludeFiles(temp_mailfile)) {
205 filterInfo()->addInfoLogEntry(i18n("Start import file %1...", temp_mailfile));
206 importMBox(mailDir() + temp_mailfile, temp_mailfile, QString());
207 }
208 }
209
210 filterInfo()->addInfoLogEntry(i18n("Finished importing emails from %1", mailDir()));
211 if (countDuplicates() > 0) {
212 filterInfo()->addInfoLogEntry(i18np("1 duplicate message not imported", "%1 duplicate messages not imported", countDuplicates()));
213 }
214 }
215 if (filterInfo()->shouldTerminate()) {
216 filterInfo()->addInfoLogEntry(i18n("Finished import, canceled by user."));
217 }
218 filterInfo()->setCurrent(100);
219 filterInfo()->setOverall(100);
220}
221
222/**
223 * Import of a directory contents.
224 * @param info Information storage for the operation.
225 * @param dirName The name of the directory to import.
226 * @param KMailRootDir The directory's root directory in KMail's folder structure.
227 * @param KMailSubDir The directory's direct ancestor in KMail's folder structure.
228 */
229void FilterThunderbird::importDirContents(const QString &dirName, const QString &KMailRootDir, const QString &KMailSubDir)
230{
231 if (filterInfo()->shouldTerminate()) {
232 return;
233 }
234 /** Here Import all archives in the current dir */
235 QDir importDir(dirName);
236 const QStringList files = importDir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Files, QDir::Name);
237 QStringList::ConstIterator mailFileEnd = files.constEnd();
238 for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != mailFileEnd; ++mailFile) {
239 if (filterInfo()->shouldTerminate()) {
240 break;
241 }
242 QString temp_mailfile = *mailFile;
243 if (!excludeFiles(temp_mailfile)) {
244 filterInfo()->addInfoLogEntry(i18n("Start import file %1...", temp_mailfile));
245 importMBox((dirName + QLatin1Char('/') + temp_mailfile), KMailRootDir, KMailSubDir);
246 }
247 }
248
249 /** If there are subfolders, we import them one by one */
250 QDir subfolders(dirName);
251 const QStringList subDirs = subfolders.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Dirs, QDir::Name);
253 for (QStringList::ConstIterator filename = subDirs.constBegin(); filename != end; ++filename) {
254 if (filterInfo()->shouldTerminate()) {
255 break;
256 }
257 QString kSubDir;
258 if (!KMailSubDir.isNull()) {
259 kSubDir = KMailSubDir + QLatin1Char('/') + *filename;
260 } else {
261 kSubDir = *filename;
262 }
263 importDirContents(subfolders.filePath(*filename), KMailRootDir, kSubDir);
264 }
265}
266
267/**
268 * Import of a MBox file.
269 * @param info Information storage for the operation.
270 * @param dirName The MBox's name.
271 * @param KMailRootDir The directory's root directory in KMail's folder structure.
272 * @param KMailSubDir The directory's equivalent in KMail's folder structure. *
273 */
274void FilterThunderbird::importMBox(const QString &mboxName, const QString &rootDir, const QString &targetDir)
275{
276 QFile mbox(mboxName);
277 if (!mbox.open(QIODevice::ReadOnly)) {
278 filterInfo()->alert(i18n("Unable to open %1, skipping", mboxName));
279 } else {
280 bool first_msg = true;
281 QFileInfo filenameInfo(mboxName);
282
283 filterInfo()->setCurrent(0);
284 if (mboxName.length() > 20) {
285 QString tmp_info = mboxName;
286 tmp_info.replace(mailDir(), QStringLiteral("../"));
287 if (tmp_info.contains(QLatin1StringView(".sbd"))) {
288 tmp_info.remove(QStringLiteral(".sbd"));
289 }
290 filterInfo()->setFrom(tmp_info);
291 } else {
292 filterInfo()->setFrom(mboxName);
293 }
294 if (targetDir.contains(QLatin1StringView(".sbd"))) {
295 QString tmp_info = targetDir;
296 tmp_info.remove(QStringLiteral(".sbd"));
297 filterInfo()->setTo(tmp_info);
298 } else {
299 filterInfo()->setTo(targetDir);
300 }
301
302 QByteArray input(MAX_LINE, '\0');
303 long l = 0;
304
305 while (!mbox.atEnd()) {
306 QTemporaryFile tmp;
307 tmp.open();
308 /** @todo check if the file is really a mbox, maybe search for 'from' string at start */
309 /* comment by Danny:
310 * Don't use QTextStream to read from mbox, better use QDataStream. QTextStream only
311 * support Unicode/Latin1/Locale. So you lost information from emails with
312 * charset!=Unicode/Latin1/Locale (e.g. KOI8-R) and Content-Transfer-Encoding != base64
313 * (e.g. 8Bit). It also not help to convert the QTextStream to Unicode. By this you
314 * get Unicode/UTF-email but KMail can't detect the correct charset.
315 */
316 QByteArray separate;
317
318 if (!first_msg) {
319 tmp.write(input.constData(), l);
320 }
321 l = mbox.readLine(input.data(), MAX_LINE); // read the first line, prevent "From "
322 tmp.write(input.constData(), l);
323
324 while (!mbox.atEnd() && (l = mbox.readLine(input.data(), MAX_LINE)) && ((separate = input.data()).left(5) != "From ")) {
325 tmp.write(input.constData(), l);
326 }
327 tmp.flush();
328 first_msg = false;
329
330 QString destFolder;
331 QString _targetDir = targetDir;
332 if (!targetDir.isNull()) {
333 if (_targetDir.contains(QLatin1StringView(".sbd"))) {
334 _targetDir.remove(QStringLiteral(".sbd"));
335 }
336 destFolder += defaultInstallFolder() + _targetDir + QLatin1Char('/') + filenameInfo.completeBaseName(); // mboxName;
337 } else {
338 destFolder = defaultInstallFolder() + rootDir;
339 if (destFolder.contains(QLatin1StringView(".sbd"))) {
340 destFolder.remove(QStringLiteral(".sbd"));
341 }
342 }
343 if (!importMessage(destFolder, tmp.fileName(), filterInfo()->removeDupMessage())) {
344 filterInfo()->addErrorLogEntry(i18n("Could not import %1", tmp.fileName()));
345 }
346
347 int currentPercentage = (int)(((float)mbox.pos() / filenameInfo.size()) * 100);
348 filterInfo()->setCurrent(currentPercentage);
349 if (filterInfo()->shouldTerminate()) {
350 mbox.close();
351 return;
352 }
353 }
354 mbox.close();
355 }
356}
KConfigGroup group(const QString &group)
bool hasKey(const char *key) const
QString readEntry(const char *key, const char *aDefault=nullptr) const
~FilterThunderbird() override
Destructor.
void importMails(const QString &maildir)
FilterThunderbird()
Default constructor.
The Filter class.
Definition filters.h:29
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString name(GameStandardAction id)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QString path(const QString &relativePath)
QStringList entryList(Filters filters, SortFlags sort) const const
bool exists() const const
QString homePath()
bool flush()
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, Options options)
qint64 write(const QByteArray &data)
const_reference at(qsizetype i) const const
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
qsizetype size() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
size_type count() const const
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
virtual QString fileName() const const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:54:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.