1 /*
2   SPDX-FileCopyrightText: 2012-2021 Laurent Montel <montel@kde.org>
3 
4   SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "filterbalsa.h"
8 
9 #include <KLocalizedString>
10 #include <QFileDialog>
11 
12 using namespace MailImporter;
13 
14 class MailImporter::FilterBalsaPrivate
15 {
16 public:
17     int mImportDirDone = 0;
18     int mTotalDir = 0;
19 };
20 /** Default constructor. */
FilterBalsa()21 FilterBalsa::FilterBalsa()
22     : Filter(i18n("Import Balsa Local Mails and Folder Structure"),
23              QStringLiteral("Laurent Montel"),
24              i18n("<p><b>Balsa import filter</b></p>"
25                   "<p>Select the base directory of your local Balsa mailfolder (usually ~/mail/).</p>"
26                   "<p>Since it is possible to recreate the folder structure, the folders "
27                   "will be stored under: \"Balsa-Import\".</p>"))
28     , d(new MailImporter::FilterBalsaPrivate)
29 {
30 }
31 
32 /** Destructor. */
33 FilterBalsa::~FilterBalsa() = default;
34 
isMailerFound()35 QString FilterBalsa::isMailerFound()
36 {
37     QDir directory(FilterBalsa::defaultSettingsPath());
38     if (directory.exists()) {
39         return i18nc("name of balsa application", "Balsa");
40     }
41     return {};
42 }
43 
defaultSettingsPath()44 QString FilterBalsa::defaultSettingsPath()
45 {
46     return QDir::homePath() + QLatin1String("/.balsa/");
47 }
48 
localMailDirPath()49 QString FilterBalsa::localMailDirPath()
50 {
51     return QDir::homePath() + QLatin1String("/mail/");
52 }
53 
54 /** Recursive import of KMail maildir. */
import()55 void FilterBalsa::import()
56 {
57     clearCountDuplicate();
58     QString balsaDir = localMailDirPath();
59     QDir d(balsaDir);
60     if (!d.exists()) {
61         balsaDir = QDir::homePath();
62     }
63     // Select directory from where I have to import files
64     const QString maildir = QFileDialog::getExistingDirectory(nullptr, QString(), balsaDir);
65     importMails(maildir);
66 }
67 
processDirectory(const QString & path)68 void FilterBalsa::processDirectory(const QString &path)
69 {
70     QDir dir(path);
71     const QStringList rootSubDirs = dir.entryList(QStringList(QStringLiteral("*")), QDir::Dirs | QDir::Hidden, QDir::Name);
72     QStringList::ConstIterator end = rootSubDirs.constEnd();
73     for (QStringList::ConstIterator filename = rootSubDirs.constBegin(); filename != end; ++filename) {
74         if (filterInfo()->shouldTerminate()) {
75             break;
76         }
77         if (!(*filename == QLatin1Char('.') || *filename == QLatin1String(".."))) {
78             filterInfo()->setCurrent(0);
79             importDirContents(dir.filePath(*filename));
80             filterInfo()->setOverall((d->mTotalDir > 0) ? (int)((float)d->mImportDirDone / d->mTotalDir * 100) : 0);
81             filterInfo()->setCurrent(100);
82         }
83     }
84 }
85 
importMails(const QString & maildir)86 void FilterBalsa::importMails(const QString &maildir)
87 {
88     if (maildir.isEmpty()) {
89         filterInfo()->alert(i18n("No directory selected."));
90         return;
91     }
92     setMailDir(maildir);
93     /**
94      * If the user only select homedir no import needed because
95      * there should be no files and we surely import wrong files.
96      */
97     if (mailDir() == QDir::homePath() || mailDir() == (QDir::homePath() + QLatin1Char('/'))) {
98         filterInfo()->addErrorLogEntry(i18n("No files found for import."));
99     } else {
100         filterInfo()->setOverall(0);
101         d->mImportDirDone = 0;
102 
103         /** Recursive import of the MailArchives */
104         QDir dir(mailDir());
105         d->mTotalDir = Filter::countDirectory(dir, true /*search hidden directory*/);
106 
107         processDirectory(mailDir());
108 
109         filterInfo()->addInfoLogEntry(i18n("Finished importing emails from %1", mailDir()));
110 
111         if (countDuplicates() > 0) {
112             filterInfo()->addInfoLogEntry(i18np("1 duplicate message not imported", "%1 duplicate messages not imported", countDuplicates()));
113         }
114 
115         if (filterInfo()->shouldTerminate()) {
116             filterInfo()->addInfoLogEntry(i18n("Finished import, canceled by user."));
117         }
118     }
119     filterInfo()->setCurrent(100);
120     filterInfo()->setOverall(100);
121 }
122 
123 /**
124  * Import of a directory contents.
125  * @param info Information storage for the operation.
126  * @param dirName The name of the directory to import.
127  */
importDirContents(const QString & dirName)128 void FilterBalsa::importDirContents(const QString &dirName)
129 {
130     /** Here Import all archives in the current dir */
131     importFiles(dirName);
132 
133     /** If there are subfolders, we import them one by one */
134     processDirectory(dirName);
135 }
136 
137 /**
138  * Import the files within a Folder.
139  * @param info Information storage for the operation.
140  * @param dirName The name of the directory to import.
141  */
importFiles(const QString & dirName)142 void FilterBalsa::importFiles(const QString &dirName)
143 {
144     QDir dir(dirName);
145     QString _path;
146     bool generatedPath = false;
147 
148     QDir importDir(dirName);
149     const QStringList files = importDir.entryList(QStringList(QStringLiteral("[^\\.]*")), QDir::Files, QDir::Name);
150     int currentFile = 1;
151     int numFiles = files.size();
152     QStringList::ConstIterator filesEnd(files.constEnd());
153 
154     for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != filesEnd; ++mailFile, ++currentFile) {
155         if (filterInfo()->shouldTerminate()) {
156             return;
157         }
158         QString temp_mailfile = *mailFile;
159         if (!(temp_mailfile.endsWith(QLatin1String(".db")) || temp_mailfile.endsWith(QLatin1String(".cmeta"))
160               || temp_mailfile.endsWith(QLatin1String(".ev-summary")) || temp_mailfile.endsWith(QLatin1String(".ibex.index"))
161               || temp_mailfile.endsWith(QLatin1String(".ibex.index.data")))) {
162             if (!generatedPath) {
163                 _path = i18nc("define folder name where we import evolution mails", "Evolution-Import");
164                 QString _tmp = dir.filePath(*mailFile);
165                 _tmp.remove(mailDir(), Qt::CaseSensitive);
166                 QStringList subFList = _tmp.split(QLatin1Char('/'), Qt::SkipEmptyParts);
167                 QStringList::ConstIterator end(subFList.end());
168                 for (QStringList::ConstIterator it = subFList.constBegin(); it != end; ++it) {
169                     QString _cat = *it;
170                     if (!(_cat == *mailFile)) {
171                         if (_cat.startsWith(QLatin1Char('.'))) {
172                             _cat.remove(0, 1);
173                         }
174                         // Evolution store inbox as "."
175                         if (_cat.startsWith(QLatin1Char('.'))) {
176                             _cat.replace(0, 1, QStringLiteral("Inbox/"));
177                         }
178 
179                         _path += QLatin1Char('/') + _cat;
180                         _path.replace(QLatin1Char('.'), QLatin1Char('/'));
181                     }
182                 }
183                 if (_path.endsWith(QLatin1String("cur"))) {
184                     _path.remove(_path.length() - 4, 4);
185                 }
186                 QString _info = _path;
187                 filterInfo()->addInfoLogEntry(i18n("Import folder %1...", _info));
188                 filterInfo()->setFrom(_info);
189                 filterInfo()->setTo(_path);
190                 generatedPath = true;
191             }
192             const MailImporter::MessageStatus status = statusFromFile(*mailFile);
193 
194             if (!importMessage(_path, dir.filePath(*mailFile), filterInfo()->removeDupMessage(), status)) {
195                 filterInfo()->addErrorLogEntry(i18n("Could not import %1", *mailFile));
196             }
197             filterInfo()->setCurrent((int)((float)currentFile / numFiles * 100));
198         }
199     }
200 }
201 
statusFromFile(const QString & filename)202 MailImporter::MessageStatus FilterBalsa::statusFromFile(const QString &filename)
203 {
204     MailImporter::MessageStatus status;
205     const int statusIndex = filename.indexOf(QLatin1String(":2,"));
206     if (statusIndex != -1) {
207         const QString statusStr = filename.right(filename.length() - statusIndex - 3);
208         if (statusStr.contains(QLatin1Char('S'))) {
209             status.setRead(true);
210         }
211         if (statusStr.contains(QLatin1Char('F'))) {
212             // TODO
213         }
214         if (statusStr.contains(QLatin1Char('R'))) {
215             status.setReplied(true);
216         }
217         if (statusStr.contains(QLatin1Char('P'))) {
218             status.setForwarded(true);
219         }
220     }
221     return status;
222 }
223