MailImporter

filtersylpheed.cpp
1/*
2 filtersylpheed.cpp - Sylpheed maildir mail import
3
4 SPDX-FileCopyrightText: 2005 Danny Kukawka <danny.kukawka@web.de>
5 SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "filtersylpheed.h"
11
12#include "mailimporter_debug.h"
13#include <KLocalizedString>
14#include <QDomDocument>
15#include <QDomElement>
16#include <QFileDialog>
17
18using namespace MailImporter;
19
20class MailImporter::FilterSylpheedPrivate
21{
22public:
23 int mImportDirDone = 0;
24 int mTotalDir = 0;
25};
26/** Default constructor. */
28 : Filter(i18n("Import Sylpheed Maildirs and Folder Structure"),
29 QStringLiteral("Danny Kukawka"),
30 i18n("<p><b>Sylpheed import filter</b></p>"
31 "<p>Select the base directory of the Sylpheed mailfolder you want to import "
32 "(usually: ~/Mail ).</p>"
33 "<p>Since it is possible to recreate the folder structure, the folders "
34 "will be stored under: \"Sylpheed-Import\" in your local folder.</p>"
35 "<p>This filter also recreates the status of message, e.g. new or forwarded.</p>"))
36 , d(new MailImporter::FilterSylpheedPrivate)
37{
38}
39
40/** Destructor. */
42
43QString FilterSylpheed::isMailerFound()
44{
45 QDir directory(FilterSylpheed::defaultSettingsPath());
46 if (directory.exists()) {
47 return i18nc("name of sylpheed application", "Sylpheed");
48 }
49 return {};
50}
51
52QString FilterSylpheed::defaultSettingsPath()
53{
54 return QDir::homePath() + QLatin1StringView("/.sylpheed-2.0/");
55}
56
57QString FilterSylpheed::localMailDirPath()
58{
59 QFile folderListFile(FilterSylpheed::defaultSettingsPath() + QLatin1StringView("/folderlist.xml"));
60 if (folderListFile.exists()) {
61 QDomDocument doc;
62 if (!folderListFile.open(QIODevice::ReadOnly)) {
63 qCWarning(MAILIMPORTER_LOG) << "Impossible to open " << folderListFile.fileName();
64 }
65 const QDomDocument::ParseResult parseResult = doc.setContent(&folderListFile);
66 if (!parseResult) {
67 qCDebug(MAILIMPORTER_LOG) << "Unable to load document.Parse error in line " << parseResult.errorLine << ", col " << parseResult.errorColumn << ": "
68 << qPrintable(parseResult.errorMessage);
69 return QString();
70 }
71 const QDomElement settings = doc.documentElement();
72
73 if (settings.isNull()) {
74 return QString();
75 }
76
77 for (QDomElement e = settings.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
78 if (e.tagName() == QLatin1StringView("folder")) {
79 if (e.hasAttribute(QStringLiteral("type"))) {
80 if (e.attribute(QStringLiteral("type")) == QLatin1StringView("mh")) {
81 return e.attribute(QStringLiteral("path"));
82 }
83 }
84 }
85 }
86 }
87 return QString();
88}
89
90/** Recursive import of Sylpheed maildir. */
92{
93 QString homeDir = localMailDirPath();
94 if (homeDir.isEmpty()) {
95 homeDir = QDir::homePath();
96 }
97 // Select directory from where I have to import files
98 const QString maildir = QFileDialog::getExistingDirectory(nullptr, QString(), homeDir);
99 if (!maildir.isEmpty()) {
100 importMails(maildir);
101 }
102}
103
104void FilterSylpheed::processDirectory(const QString &path)
105{
106 QDir dir(path);
107 const QStringList rootSubDirs = dir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Dirs, QDir::Name);
108 QStringList::ConstIterator end = rootSubDirs.constEnd();
109 for (QStringList::ConstIterator filename = rootSubDirs.constBegin(); filename != end; ++filename) {
110 if (filterInfo()->shouldTerminate()) {
111 break;
112 }
113 importDirContents(dir.filePath(*filename));
114 filterInfo()->setOverall((d->mTotalDir > 0) ? (int)((float)d->mImportDirDone / d->mTotalDir * 100) : 0);
115 ++d->mImportDirDone;
116 }
117}
118
120{
121 if (maildir.isEmpty()) {
122 filterInfo()->alert(i18n("No directory selected."));
123 return;
124 }
125 setMailDir(maildir);
126 /**
127 * If the user only select homedir no import needed because
128 * there should be no files and we surely import wrong files.
129 */
130 if (mailDir() == QDir::homePath() || mailDir() == (QDir::homePath() + QLatin1Char('/'))) {
131 filterInfo()->addErrorLogEntry(i18n("No files found for import."));
132 } else {
133 filterInfo()->setOverall(0);
134
135 d->mImportDirDone = 0;
136
137 /** Recursive import of the MailFolders */
138 QDir dir(mailDir());
139
140 d->mTotalDir = Filter::countDirectory(dir, false);
141 processDirectory(mailDir());
142
143 filterInfo()->addInfoLogEntry(i18n("Finished importing emails from %1", mailDir()));
144 if (countDuplicates() > 0) {
145 filterInfo()->addInfoLogEntry(i18np("1 duplicate message not imported", "%1 duplicate messages not imported", countDuplicates()));
146 }
147 }
148 if (filterInfo()->shouldTerminate()) {
149 filterInfo()->addInfoLogEntry(i18n("Finished import, canceled by user."));
150 }
151 clearCountDuplicate();
152 filterInfo()->setCurrent(100);
153 filterInfo()->setOverall(100);
154}
155
156/**
157 * Import of a directory contents.
158 * @param info Information storage for the operation.
159 * @param dirName The name of the directory to import.
160 */
161void FilterSylpheed::importDirContents(const QString &dirName)
162{
163 if (filterInfo()->shouldTerminate()) {
164 return;
165 }
166
167 /** Here Import all archives in the current dir */
168 importFiles(dirName);
169
170 /** If there are subfolders, we import them one by one */
171 processDirectory(dirName);
172}
173
174bool FilterSylpheed::excludeFile(const QString &file)
175{
176 if (file.endsWith(QLatin1StringView(".sylpheed_cache")) || file.endsWith(QLatin1StringView(".sylpheed_mark"))
177 || file.endsWith(QLatin1StringView(".mh_sequences"))) {
178 return true;
179 }
180 return false;
181}
182
183QString FilterSylpheed::defaultInstallFolder() const
184{
185 return i18nc("define folder name where we will import sylpheed mails", "Sylpheed-Import") + QLatin1Char('/');
186}
187
188QString FilterSylpheed::markFile() const
189{
190 return QStringLiteral(".sylpheed_mark");
191}
192
193/**
194 * Import the files within a Folder.
195 * @param info Information storage for the operation.
196 * @param dirName The name of the directory to import.
197 */
198void FilterSylpheed::importFiles(const QString &dirName)
199{
200 QDir dir(dirName);
201 QString _path;
202 bool generatedPath = false;
203
205
206 QDir importDir(dirName);
207 const QString defaultInstallPath = defaultInstallFolder();
208
209 const QStringList files = importDir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Files, QDir::Name);
210 int currentFile = 1;
211 int numFiles = files.size();
212
213 readMarkFile(dir.filePath(markFile()), msgflags);
214
216 for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != end; ++mailFile, ++currentFile) {
217 if (filterInfo()->shouldTerminate()) {
218 return;
219 }
220 QString _mfile = *mailFile;
221 if (!excludeFile(_mfile)) {
222 if (!generatedPath) {
223 _path = defaultInstallPath;
224 QString _tmp = dir.filePath(*mailFile);
225 _tmp.remove(_tmp.length() - _mfile.length() - 1, _mfile.length() + 1);
226 _path += _tmp.remove(mailDir(), Qt::CaseSensitive);
227 QString _info = _path;
228 filterInfo()->addInfoLogEntry(i18n("Import folder %1...", _info.remove(0, 15)));
229
230 filterInfo()->setFrom(_info);
231 filterInfo()->setTo(_path);
232 generatedPath = true;
233 }
234
236 if (msgflags[_mfile]) {
237 status = msgFlagsToString((msgflags[_mfile]));
238 } else {
239 status.setRead(true); // 0 == read
240 }
241 if (!importMessage(_path, dir.filePath(*mailFile), filterInfo()->removeDupMessage(), status)) {
242 filterInfo()->addErrorLogEntry(i18n("Could not import %1", *mailFile));
243 }
244 filterInfo()->setCurrent((int)((float)currentFile / numFiles * 100));
245 }
246 }
247}
248
249void FilterSylpheed::readMarkFile(const QString &path, QHash<QString, unsigned long> &dict)
250{
251 /* Each sylpheed mail directory contains a .sylpheed_mark file which
252 * contains all the flags for each messages. The layout of this file
253 * is documented in the source code of sylpheed: in procmsg.h for
254 * the flag bits, and procmsg.c.
255 *
256 * Note that the mark file stores 32 bit unsigned integers in the
257 * platform's native "endianness".
258 *
259 * The mark file starts with a 32 bit unsigned integer with a version
260 * number. It is then followed by pairs of 32 bit unsigned integers,
261 * the first one with the message file name (which is a number),
262 * and the second one with the actual message flags */
263
264 quint32 in;
265 quint32 flags;
266 QFile file(path);
267
268 if (!file.open(QIODevice::ReadOnly)) {
269 return;
270 }
271
272 QDataStream stream(&file);
273
274 if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN) {
275 stream.setByteOrder(QDataStream::LittleEndian);
276 }
277
278 /* Read version; if the value is reasonably too big, we're looking
279 * at a file created on another platform. I don't have any test
280 * marks/folders, so just ignoring this case */
281 stream >> in;
282 if (in > (quint32)0xffff) {
283 return;
284 }
285
286 while (!stream.atEnd()) {
287 if (filterInfo()->shouldTerminate()) {
288 file.close();
289 return;
290 }
291 stream >> in;
292 stream >> flags;
293 QString s;
294 s.setNum((uint)in);
295 dict.insert(s, flags);
296 }
297}
298
299MailImporter::MessageStatus FilterSylpheed::msgFlagsToString(unsigned long flags)
300{
302 /* see sylpheed's procmsg.h */
303 if (flags & 2UL) {
304 status.setRead(false);
305 }
306 if ((flags & 3UL) == 0UL) {
307 status.setRead(true);
308 }
309 if (flags & 8UL) {
310 status.setDeleted(true);
311 }
312 if (flags & 16UL) {
313 status.setReplied(true);
314 }
315 if (flags & 32UL) {
316 status.setForwarded(true);
317 }
318 return status;
319}
~FilterSylpheed() override
Destructor.
virtual void importMails(const QString &maildir)
void import() override
Recursive import of Sylpheed maildir.
FilterSylpheed()
Default constructor.
The Filter class.
Definition filters.h:29
The MessageStatus class.
Q_SCRIPTABLE CaptureState status()
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...)
KIOCORE_EXPORT QString dir(const QString &fileClass)
const QList< QKeySequence > & end()
QString homePath()
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QDomElement firstChildElement(const QString &tagName, const QString &namespaceURI) const const
bool isNull() const const
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, Options options)
iterator insert(const Key &key, const T &value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype size() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & setNum(double n, char format, int precision)
CaseSensitive
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:13:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.