1 /*
2   filterthebat.cpp  -  TheBat! mail import
3 
4   SPDX-FileCopyrightText: 2005 Danny Kukawka <danny.kukawka@web.de>
5   SPDX-FileCopyrightText: 2012-2021 Laurent Montel <montel@kde.org>
6 
7   SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "filterthebat.h"
11 
12 #include <KLocalizedString>
13 #include <QFileDialog>
14 #include <QRegularExpression>
15 #include <QTemporaryFile>
16 
17 using namespace MailImporter;
18 
19 class MailImporter::FilterTheBatPrivate
20 {
21 public:
22     int mImportDirDone = 0;
23     int mTotalDir = 0;
24 };
25 
26 /** Default constructor. */
FilterTheBat()27 FilterTheBat::FilterTheBat()
28     : Filter(i18n("Import The Bat! Mails and Folder Structure"),
29              QStringLiteral("Danny Kukawka"),
30              i18n("<p><b>The Bat! import filter</b></p>"
31                   "<p>Select the base directory of the \'The Bat!\' local mailfolder you "
32                   "want to import.</p>"
33                   "<p><b>Note:</b> This filter imports the *.tbb-files from \'The Bat!\' "
34                   "local folder, e.g. from POP accounts, and not from IMAP/DIMAP accounts.</p>"
35                   "<p>Since it is possible to recreate the folder structure, the folders "
36                   "will be stored under: \"TheBat-Import\" in your local account.</p>"))
37     , d(new MailImporter::FilterTheBatPrivate)
38 {
39 }
40 
41 /** Destructor. */
42 FilterTheBat::~FilterTheBat() = default;
43 
44 /** Recursive import of The Bat! maildir. */
import()45 void FilterTheBat::import()
46 {
47     const QString _homeDir = QDir::homePath();
48     // Select directory from where I have to import files
49     const QString maildir = QFileDialog::getExistingDirectory(nullptr, QString(), _homeDir);
50     if (!maildir.isEmpty()) {
51         importMails(maildir);
52     }
53 }
54 
processDirectory(const QString & path)55 void FilterTheBat::processDirectory(const QString &path)
56 {
57     QDir dir(path);
58     const QStringList rootSubDirs = dir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Dirs, QDir::Name);
59     QStringList::ConstIterator end = rootSubDirs.constEnd();
60     for (QStringList::ConstIterator filename = rootSubDirs.constBegin(); filename != end; ++filename) {
61         if (filterInfo()->shouldTerminate()) {
62             break;
63         }
64         importDirContents(dir.filePath(*filename));
65         filterInfo()->setOverall((d->mTotalDir > 0) ? (int)((float)d->mImportDirDone / d->mTotalDir * 100) : 0);
66         ++d->mImportDirDone;
67     }
68 }
69 
importMails(const QString & maildir)70 void FilterTheBat::importMails(const QString &maildir)
71 {
72     if (maildir.isEmpty()) {
73         filterInfo()->alert(i18n("No directory selected."));
74         return;
75     }
76     setMailDir(maildir);
77     /**
78      * If the user only select homedir no import needed because
79      * there should be no files and we surely import wrong files.
80      */
81     if (mailDir() == QDir::homePath() || mailDir() == (QDir::homePath() + QLatin1Char('/'))) {
82         filterInfo()->addErrorLogEntry(i18n("No files found for import."));
83     } else {
84         filterInfo()->setOverall(0);
85         d->mImportDirDone = 0;
86 
87         /** Recursive import of the MailFolders */
88         QDir dir(mailDir());
89         d->mTotalDir = Filter::countDirectory(dir, false);
90 
91         processDirectory(mailDir());
92 
93         filterInfo()->addInfoLogEntry(i18n("Finished importing emails from %1", mailDir()));
94         if (countDuplicates() > 0) {
95             filterInfo()->addInfoLogEntry(i18np("1 duplicate message not imported", "%1 duplicate messages not imported", countDuplicates()));
96         }
97     }
98     if (filterInfo()->shouldTerminate()) {
99         filterInfo()->addInfoLogEntry(i18n("Finished import, canceled by user."));
100     }
101 
102     clearCountDuplicate();
103     filterInfo()->setCurrent(100);
104     filterInfo()->setOverall(100);
105 }
106 
107 /**
108  * Import of a directory contents.
109  * @param info Information storage for the operation.
110  * @param dirName The name of the directory to import.
111  */
importDirContents(const QString & dirName)112 void FilterTheBat::importDirContents(const QString &dirName)
113 {
114     if (filterInfo()->shouldTerminate()) {
115         return;
116     }
117 
118     /** Here Import all archives in the current dir */
119     QDir importDir(dirName);
120     const QStringList files = importDir.entryList(QStringList(QStringLiteral("*.[tT][bB][bB]")), QDir::Files, QDir::Name);
121     QStringList::ConstIterator end = files.constEnd();
122     for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != end; ++mailFile) {
123         QString temp_mailfile = *mailFile;
124         importFiles((dirName + QLatin1Char('/') + temp_mailfile));
125         if (filterInfo()->shouldTerminate()) {
126             return;
127         }
128     }
129 
130     /** If there are subfolders, we import them one by one */
131     processDirectory(dirName);
132 }
133 
134 /**
135  * Import the files within a Folder.
136  * @param info Information storage for the operation.
137  * @param dirName The name of the directory to import.
138  */
importFiles(const QString & FileName)139 void FilterTheBat::importFiles(const QString &FileName)
140 {
141     // Format of a tbb-file from The Bat! 3.x
142     // ----------------------------------------
143     // First comes a header of 3K (3128 byte/ 0x00000c38), which we can forget.
144     // The byte 3129 is the first character of the first message.
145     //
146     // The end of a message is marked through "! p 0" and 43 following characters.
147     // (within: "_UB", blanks and some other chars.) Together are 48 characters as
148     // separator.
149     // ----------------------------------------
150 
151     long l = 0;
152     QByteArray input(50, '\0');
153     const QRegularExpression regexp(QStringLiteral("!.p.0"));
154     QFile tbb(FileName);
155     int iFound = 0;
156     int count = 0;
157     long endOfEmail = 0;
158     QList<long> offsets;
159 
160     if (!tbb.open(QIODevice::ReadOnly)) {
161         filterInfo()->alert(i18n("Unable to open %1, skipping", FileName));
162     } else {
163         // BUILD the index of messages :
164         // We need this really ugly way, because read with tbb.readLine()
165         // does not work correct. Maybe in come in a continuous loop !!!
166         // Reason:
167         //      if you use readLine() to read from a file with binary data
168         //      QFile::at() and QFile::atEnd() return wrong value. So we
169         //      never get QFile::atEnd() == true in some cases. This looks
170         //      like a bug in Qt3 maybe fixed in Qt4.
171         //
172         while ((l = tbb.read(input.data(), 50))) {
173             if (filterInfo()->shouldTerminate()) {
174                 tbb.close();
175                 return;
176             }
177             QString _tmp = QString::fromUtf8(input.data());
178 
179             if (tbb.atEnd()) {
180                 break;
181             }
182 
183             iFound = _tmp.count(regexp);
184             if (!iFound) {
185                 iFound = _tmp.lastIndexOf(QLatin1Char('!'));
186                 if (iFound >= 0 && ((l - iFound) < 5)) {
187                     int _i = tbb.pos();
188                     tbb.seek((_i - iFound));
189                 }
190             } else {
191                 ++count;
192                 endOfEmail = (tbb.pos() - l + _tmp.indexOf(regexp));
193                 offsets.append(endOfEmail);
194             }
195         }
196 
197         // IMPORT the messages:
198         if (!offsets.empty() || (offsets.empty() && (tbb.size() > 3128))) {
199             offsets.append(tbb.size());
200             tbb.seek(3128);
201             long lastPos = 3128;
202             long endPos = 0;
203 
204             QString _path = i18nc("Define folder where we will import thebat mails", "TheBat-Import") + QLatin1Char('/');
205             QString _tmp = FileName;
206             _tmp.remove(_tmp.length() - 13, 13);
207             _path += _tmp.remove(mailDir(), Qt::CaseSensitive);
208             QString _info = _path;
209             filterInfo()->addInfoLogEntry(i18n("Import folder %1...", _info.remove(0, 14)));
210             filterInfo()->setTo(_path);
211             filterInfo()->setFrom(QLatin1String("../") + _info + QLatin1String("/messages.tbb"));
212 
213             QList<long>::Iterator end = offsets.end();
214             for (QList<long>::Iterator it = offsets.begin(); it != end; ++it) {
215                 if (filterInfo()->shouldTerminate()) {
216                     tbb.close();
217                     return;
218                 }
219                 endPos = *it;
220                 QByteArray input(endPos - lastPos, '\0');
221                 tbb.read(input.data(), endPos - lastPos);
222 
223                 QTemporaryFile tmp;
224                 tmp.open();
225                 tmp.write(input.constData(), endPos - lastPos);
226                 tmp.flush();
227 
228                 if (!importMessage(_path, tmp.fileName(), filterInfo()->removeDupMessage())) {
229                     filterInfo()->addErrorLogEntry(i18n("Could not import %1", tmp.fileName()));
230                 }
231 
232                 lastPos = endPos + 48;
233                 tbb.seek(lastPos);
234                 filterInfo()->setCurrent((int)(((float)tbb.pos() / tbb.size()) * 100));
235             }
236         }
237     }
238     tbb.close();
239 }
240