MailImporter

filterpmail.cpp
1/*
2 filterpmail.cpp - Pegasus-Mail import
3
4 SPDX-FileCopyrightText: 2001 Holger Schurig <holgerschurig@gmx.de>
5 SPDX-FileCopyrightText: 2005 Danny Kukawka <danny.kukawka@web.de>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "filterpmail.h"
11#include "mailimporter_debug.h"
12
13#include <KLocalizedString>
14
15#include <QFileDialog>
16#include <QRegularExpression>
17#include <QTemporaryFile>
18
19using namespace MailImporter;
20
21FilterPMail::FilterPMail()
22 : Filter(i18n("Import Folders From Pegasus-Mail"),
23 i18n("Holger Schurig <br>( rewritten by Danny Kukawka )"),
24 i18n("<p>Select the Pegasus-Mail directory on your system (containing *.CNM, *.PMM and *.MBX files). "
25 "On many systems this is stored in C:\\pmail\\mail or C:\\pmail\\mail\\admin</p>"
26 "<p><b>Note:</b> Since it is possible to recreate the folder structure, the folders "
27 "will be stored under: \"PegasusMail-Import\".</p>"))
28{
29}
30
31FilterPMail::~FilterPMail()
32{
33}
34
35void FilterPMail::import()
36{
37 // Select directory from where I have to import files
39 importMails(maildir);
40}
41
42void FilterPMail::importMails(const QString &chosenDir)
43{
44 if (chosenDir.isEmpty()) {
45 filterInfo()->alert(i18n("No directory selected."));
46 return;
47 }
48 setMailDir(chosenDir);
49
50 // Count total number of files to be processed
51 filterInfo()->addInfoLogEntry(i18n("Counting files..."));
52 dir.setPath(mailDir());
53 const QStringList files =
54 dir.entryList(QStringList() << QStringLiteral("*.[cC][nN][mM]") << QStringLiteral("*.[pP][mM][mM]") << QStringLiteral("*.[mM][bB][xX]"),
57 totalFiles = files.count();
58 currentFile = 0;
59 qCDebug(MAILIMPORTER_LOG) << "Count is" << totalFiles;
60
61 if (!(folderParsed = parseFolderMatrix(mailDir()))) {
62 filterInfo()->addErrorLogEntry(i18n("Cannot parse the folder structure; continuing import without subfolder support."));
63 }
64
65 filterInfo()->addInfoLogEntry(i18n("Importing new mail files ('.cnm')..."));
66 processFiles(QStringLiteral("*.[cC][nN][mM]"), &FilterPMail::importNewMessage);
67 filterInfo()->addInfoLogEntry(i18n("Importing mail folders ('.pmm')..."));
68 processFiles(QStringLiteral("*.[pP][mM][mM]"), &FilterPMail::importMailFolder);
69 filterInfo()->addInfoLogEntry(i18n("Importing 'UNIX' mail folders ('.mbx')..."));
70 processFiles(QStringLiteral("*.[mM][bB][xX]"), &FilterPMail::importUnixMailFolder);
71
72 filterInfo()->addInfoLogEntry(i18n("Finished importing emails from %1", mailDir()));
73 filterInfo()->setCurrent(100);
74 filterInfo()->setOverall(100);
75}
76
77/** this looks for all files with the filemask 'mask' and calls the 'workFunc' on each of them */
78void FilterPMail::processFiles(const QString &mask, void (FilterPMail::*workFunc)(const QString &))
79{
80 if (filterInfo()->shouldTerminate()) {
81 return;
82 }
83
84 const QStringList files = dir.entryList(QStringList(mask), QDir::Files, QDir::Name);
85 // qCDebug(MAILIMPORTER_LOG) <<"Mask is" << mask <<" count is" << files.count();
87 for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != end; ++mailFile) {
88 // Notify current file
89 QFileInfo mailfilem_filterInfoo(*mailFile);
90 filterInfo()->setFrom(mailfilem_filterInfoo.fileName());
91
92 // Clear the other fields
93 filterInfo()->setTo(QString());
94 filterInfo()->setCurrent(QString());
95 filterInfo()->setCurrent(-1);
96
97 // call worker function, increase progressbar
98 (this->*workFunc)(dir.filePath(*mailFile));
99 ++currentFile;
100 filterInfo()->setOverall((int)((float)currentFile / totalFiles * 100));
101 filterInfo()->setCurrent(100);
102 if (filterInfo()->shouldTerminate()) {
103 return;
104 }
105 }
106}
107
108/** this function imports one *.CNM message */
110{
111 QString destFolder(QStringLiteral("PegasusMail-Import/New Messages"));
112 filterInfo()->setTo(destFolder);
113
114 if (!importMessage(destFolder, file, filterInfo()->removeDupMessage())) {
115 filterInfo()->addErrorLogEntry(i18n("Could not import %1", file));
116 }
117}
118
119/** this function imports one mail folder file (*.PMM) */
121{
122 // Format of a PMM file:
123 // First comes a header with 128 bytes. At the beginning is the name of
124 // the folder. Then there are some unknown bytes (strings). At offset 128
125 // the first message starts.
126 //
127 // Each message is terminated by a 0x1A byte. The next message follows
128 // immediately.
129 //
130 // The last message is followed by a 0x1A, too.
131 //
132 // 000000 6d 61 69 6c 73 65 72 76 65 72 2d 70 72 6f 6a 65 mailserver-proje
133 // 000010 63 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ct..............
134 // 000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
135 // 000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
136 // 000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
137 // 000050 00 00 00 00 00 00 36 30 34 37 35 37 32 45 3a 36 ......6047572E:6
138 // 000060 46 34 39 3a 46 4f 4c 30 31 33 35 35 00 00 00 00 F49:FOL01355....
139 // 000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
140 // 000080 52 65 74 75 72 6e 2d 50 61 74 68 3a 20 3c 75 72 Return-Path: <ur
141 // ...
142 // 000cb0 2d 2d 2d 2d 2d 2d 2d 2d 2d 2b 0d 0a 1a 52 65 74 ---------+...Ret
143 // 000cc0 75 72 6e 2d 50 61 74 68 3a 20 3c 62 6f 75 6e 63 urn-Path: <bounc
144 // ...
145 // 04dc50 46 30 33 38 44 2e 36 31 35 44 37 34 44 30 2d 2d F038D.615D74D0--
146 // 04dc60 0d 0a 1a
147
148 struct {
149 char folder[86];
150 char id[42];
151 } pmm_head;
152
153 QFile f(file);
154 if (!f.open(QIODevice::ReadOnly)) {
155 filterInfo()->alert(i18n("Unable to open %1, skipping", file));
156 } else {
157 // Get folder name
158 long l = f.read((char *)&pmm_head, sizeof(pmm_head));
159 QString folder(i18nc("define folder name when we will import pegasus mail", "PegasusMail-Import") + QLatin1Char('/'));
160 if (folderParsed) {
161 folder.append(getFolderName(QString::fromLatin1(pmm_head.id)));
162 } else {
163 folder.append(QString::fromLatin1(pmm_head.folder));
164 }
165 filterInfo()->setTo(folder);
166 filterInfo()->addInfoLogEntry(i18n("Importing %1", QStringLiteral("../") + QString::fromLatin1(pmm_head.folder)));
167
168 QByteArray input(MAX_LINE, '\0');
169 bool first_msg = true;
170
171 while (!f.atEnd()) {
172 QTemporaryFile tempfile;
173 tempfile.open();
174 filterInfo()->setCurrent((int)(((float)f.pos() / f.size()) * 100));
175
176 if (!first_msg) {
177 // set the filepos back to last line minus the separate char (0x1a)
178 f.seek(f.pos() - l + 1);
179 }
180
181 // no problem to loose the last line in file. This only contains a separate char
182 while (!f.atEnd() && (l = f.readLine(input.data(), MAX_LINE))) {
183 if (filterInfo()->shouldTerminate()) {
184 return;
185 }
186 if (input.at(0) == 0x1a) {
187 break;
188 } else {
189 tempfile.write(input.constData(), l);
190 }
191 }
192 tempfile.flush();
193 if (!importMessage(folder, tempfile.fileName(), filterInfo()->removeDupMessage())) {
194 filterInfo()->addErrorLogEntry(i18n("Could not import %1", tempfile.fileName()));
195 }
196
197 first_msg = false;
198 }
199 }
200 f.close();
201}
202
203/** imports a 'unix' format mail folder (*.MBX) */
205{
206 struct {
207 char folder[58];
208 char id[31];
209 } pmg_head;
210
211 QFile f;
212 QString folder(QStringLiteral("PegasusMail-Import/"));
213 QString s(file);
214 QString separate;
215 QByteArray line(MAX_LINE, '\0');
216 int n = 0;
217 int l = 0;
218
219 /** Get the folder name */
220 s.replace(QRegularExpression(QStringLiteral("mbx$")), QStringLiteral("pmg"));
221 s.replace(QRegularExpression(QStringLiteral("MBX$")), QStringLiteral("PMG"));
222 f.setFileName(s);
223 if (!f.open(QIODevice::ReadOnly)) {
224 filterInfo()->alert(i18n("Unable to open %1, skipping", s));
225 return;
226 } else {
227 f.read((char *)&pmg_head, sizeof(pmg_head));
228 f.close();
229
230 if (folderParsed) {
231 folder.append(getFolderName(QString::fromLatin1(pmg_head.id)));
232 } else {
233 folder.append(QString::fromLatin1(pmg_head.folder));
234 }
235
236 filterInfo()->setTo(folder);
237 filterInfo()->setTo(folder);
238 }
239
240 /** Read in the mbox */
241 f.setFileName(file);
242 if (!f.open(QIODevice::ReadOnly)) {
243 filterInfo()->alert(i18n("Unable to open %1, skipping", s));
244 } else {
245 filterInfo()->addInfoLogEntry(i18n("Importing %1", QStringLiteral("../") + QString::fromLatin1(pmg_head.folder)));
246 l = f.readLine(line.data(), MAX_LINE); // read the first line which is unneeded
247 while (!f.atEnd()) {
248 QTemporaryFile tempfile;
249 tempfile.open();
250
251 // we lost the last line, which is the first line of the new message in
252 // this lopp, but this is ok, because this is the separate line with
253 // "From ???@???" and we can forget them
254 while (!f.atEnd() && (l = f.readLine(line.data(), MAX_LINE))
255 && ((separate = QString::fromLatin1(line.data())).left(5) != QLatin1StringView("From "))) {
256 tempfile.write(line.data(), l);
257 if (filterInfo()->shouldTerminate()) {
258 return;
259 }
260 }
261 tempfile.flush();
262 if (!importMessage(folder, tempfile.fileName(), filterInfo()->removeDupMessage())) {
263 filterInfo()->addErrorLogEntry(i18n("Could not import %1", tempfile.fileName()));
264 }
265
266 n++;
267 filterInfo()->setCurrent(i18n("Message %1", n));
268 filterInfo()->setCurrent((int)(((float)f.pos() / f.size()) * 100));
269 }
270 }
271 f.close();
272}
273
274/** Parse the m_filterInfoormation about folderstructure to folderMatrix */
276{
277 qCDebug(MAILIMPORTER_LOG) << "Start parsing the foldermatrix.";
278 filterInfo()->addInfoLogEntry(i18n("Parsing the folder structure..."));
279
280 QFile hierarch(chosendir + QLatin1StringView("/hierarch.pm"));
281 if (!hierarch.open(QIODevice::ReadOnly)) {
282 filterInfo()->alert(i18n("Unable to open %1, skipping", chosendir + QLatin1StringView("hierarch.pm")));
283 return false;
284 } else {
285 QByteArray tmpRead;
286 while (!hierarch.atEnd()) {
287 tmpRead = hierarch.readLine();
288 if (tmpRead.isEmpty()) {
289 break;
290 }
291 QString tmpArray[5];
292 tmpRead.remove(tmpRead.length() - 2, 2);
294 int i = 0;
296 for (QStringList::ConstIterator it = tmpList.constBegin(); it != end; ++it, ++i) {
297 QString _tmp = *it;
298 if (i < 5) {
299 tmpArray[i] = _tmp.remove(QLatin1Char('\"'));
300 } else {
301 hierarch.close();
302 return false;
303 }
304 }
305 folderMatrix.append(FolderStructure(tmpArray));
306 }
307 }
308 hierarch.close();
309 return true;
310}
311
312/** get the foldername for a given file ID from folderMatrix */
314{
315 bool found = false;
316 QString folder;
317 QString search = ID;
318
319 while (!found) {
320 FolderStructureIterator end(folderMatrix.end());
321 for (FolderStructureIterator it = folderMatrix.begin(); it != end; ++it) {
322 FolderStructure tmp = *it;
323
324 QString _ID = tmp[2];
325 if (_ID == search) {
326 QString _type = tmp[0] + tmp[1];
327 if ((_type == QLatin1StringView("21"))) {
328 found = true;
329 break;
330 } else {
331 folder.prepend((tmp[4] + QLatin1Char('/')));
332 search = tmp[3];
333 }
334 }
335 }
336 }
337 return folder;
338}
The FilterPMail class.
Definition filterpmail.h:21
void importUnixMailFolder(const QString &file)
imports a 'unix' format mail folder (*.MBX)
void processFiles(const QString &mask, void(FilterPMail::*workFunc)(const QString &))
this looks for all files with the filemask 'mask' and calls the 'workFunc' on each of them
void importMailFolder(const QString &file)
this function imports one mail folder file (*.PMM)
void importNewMessage(const QString &file)
this function imports one *.CNM message
bool parseFolderMatrix(const QString &chosenDir)
this function recreate the folder structure
QString getFolderName(const QString &ID)
this function parse the folder structure
Glorified QString[N] for (a) understandability (b) older gcc compatibility.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char at(qsizetype i) const const
const char * constData() const const
char * data()
bool isEmpty() const const
qsizetype length() const const
QByteArray & remove(qsizetype pos, qsizetype len)
QStringList entryList(Filters filters, SortFlags sort) const const
QString filePath(const QString &fileName) const const
QString homePath()
void setPath(const QString &path)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void setFileName(const QString &name)
virtual qint64 size() const const override
virtual bool atEnd() const const override
virtual void close() override
bool flush()
virtual qint64 pos() const const override
virtual bool seek(qint64 pos) override
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, Options options)
QString fileName() const const
QByteArray read(qint64 maxSize)
QByteArray readLine(qint64 maxSize)
qint64 write(const QByteArray &data)
typedef ConstIterator
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
QString & append(QChar ch)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString & prepend(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
SkipEmptyParts
virtual QString fileName() const const override
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.