filterkmailarchive.cpp 8.31 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* Copyright 2009,2010 Klarälvdalens Datakonsult AB

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License or (at your option) version 3 or any later version
   accepted by the membership of KDE e.V. (or its successor approved
   by the membership of KDE e.V.), which shall act as a proxy
   defined in Section 14 of version 3 of the license.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
17
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
*/
#include "filterkmailarchive.h"

#include <KLocalizedString>
#include <QFileDialog>
#include <KZip>
#include <KTar>

#include "mailimporter_debug.h"
#include <QApplication>

#include <QSharedPointer>

#include <QMimeDatabase>
#include <QMimeType>

using namespace MailImporter;

class MailImporter::FilterKMailArchivePrivate
{
public:
    FilterKMailArchivePrivate()
    {
    }

Laurent Montel's avatar
Laurent Montel committed
43 44
    int mTotalFiles = 0;
    int mFilesDone = 0;
45 46 47 48 49 50
};
FilterKMailArchive::FilterKMailArchive()
    : Filter(i18n("Import KMail Archive File"),
             QStringLiteral("Klar\xE4lvdalens Datakonsult AB"),
             i18n("<p><b>KMail Archive File Import Filter</b></p>"
                  "<p>This filter will import archives files previously exported by KMail.</p>"
Laurent Montel's avatar
Laurent Montel committed
51 52
                  "<p>Archive files contain a complete folder subtree compressed into a single file.</p>"))
    , d(new MailImporter::FilterKMailArchivePrivate)
53 54 55 56 57 58 59 60 61 62 63 64 65
{
}

FilterKMailArchive::~FilterKMailArchive()
{
    delete d;
}

// Input: .inbox.directory
// Output: inbox
// Can also return an empty string if this is no valid dir name
static QString folderNameForDirectoryName(const QString &dirName)
{
Laurent Montel's avatar
Laurent Montel committed
66
    Q_ASSERT(dirName.startsWith(QLatin1Char('.')));
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    const QString end = QStringLiteral(".directory");
    const int expectedIndex = dirName.length() - end.length();
    if (dirName.toLower().indexOf(end) != expectedIndex) {
        return QString();
    }
    QString returnName = dirName.left(dirName.length() - end.length());
    returnName = returnName.right(returnName.length() - 1);
    return returnName;
}

bool FilterKMailArchive::importMessage(const KArchiveFile *file, const QString &folderPath)
{
    if (filterInfo()->shouldTerminate()) {
        return false;
    }

    qApp->processEvents();
84
    return filterImporter()->importMessage(file, folderPath, d->mTotalFiles, d->mFilesDone);
85 86 87 88 89 90
}

bool FilterKMailArchive::importFolder(const KArchiveDirectory *folder, const QString &folderPath)
{
    qCDebug(MAILIMPORTER_LOG) << "Importing folder" << folder->name();
    filterInfo()->addInfoLogEntry(i18n("Importing folder '%1'...", folderPath));
91
    filterInfo()->setTo(filterImporter()->topLevelFolder() + folderPath);
Laurent Montel's avatar
Laurent Montel committed
92 93
    const KArchiveDirectory *const messageDir
        = dynamic_cast<const KArchiveDirectory *>(folder->entry(QStringLiteral("cur")));
94 95 96 97
    if (messageDir) {
        int total = messageDir->entries().count();
        int cur = 1;

Laurent Montel's avatar
Laurent Montel committed
98 99
        const QStringList lstEntries = messageDir->entries();
        for (const QString &entryName : lstEntries) {
100
            filterInfo()->setCurrent(cur * 100 / total);
Laurent Montel's avatar
Laurent Montel committed
101
            filterInfo()->setOverall(d->mTotalFiles == 0 ? 0 : (d->mFilesDone * 100 / d->mTotalFiles));
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
            const KArchiveEntry *const entry = messageDir->entry(entryName);

            if (entry->isFile()) {
                const int oldCount = d->mFilesDone;
                if (!importMessage(static_cast<const KArchiveFile *>(entry), folderPath)) {
                    return false;
                }

                // Adjust the counter. Total count can decrease because importMessage() detects a duplicate
                if (oldCount != d->mFilesDone) {
                    cur++;
                } else {
                    total--;
                }
            } else {
                filterInfo()->addErrorLogEntry(i18n("Unexpected subfolder %1 in folder %2.", entryName, folder->name()));
            }
        }
    } else {
        filterInfo()->addErrorLogEntry(i18n("No subfolder named 'cur' in folder %1.", folder->name()));
    }
    return true;
}

bool FilterKMailArchive::importDirectory(const KArchiveDirectory *directory, const QString &folderPath)
{
    qCDebug(MAILIMPORTER_LOG) << "Importing directory" << directory->name();
Laurent Montel's avatar
Laurent Montel committed
129 130
    const QStringList lstEntries = directory->entries();
    for (const QString &entryName : lstEntries) {
131 132 133 134 135
        const KArchiveEntry *const entry = directory->entry(entryName);

        if (entry->isDirectory()) {
            const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(entry);

Laurent Montel's avatar
Laurent Montel committed
136
            if (!dir->name().startsWith(QLatin1Char('.'))) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
                if (!importFolder(dir, folderPath + QLatin1Char('/') + dir->name())) {
                    return false;
                }
            }
            // Entry starts with a dot, so we assume it is a subdirectory
            else {
                const QString folderName = folderNameForDirectoryName(entry->name());
                if (folderName.isEmpty()) {
                    filterInfo()->addErrorLogEntry(i18n("Unexpected subdirectory named '%1'.", entry->name()));
                } else {
                    if (!importDirectory(dir, folderPath + QLatin1Char('/') + folderName)) {
                        return false;
                    }
                }
            }
        }
    }

    return true;
}

int FilterKMailArchive::countFiles(const KArchiveDirectory *directory) const
{
    int count = 0;
Laurent Montel's avatar
Laurent Montel committed
161 162
    const QStringList lstEntries = directory->entries();
    for (const QString &entryName : lstEntries) {
163 164 165 166 167 168 169 170 171 172 173 174
        const KArchiveEntry *const entry = directory->entry(entryName);
        if (entry->isFile()) {
            count++;
        } else {
            count += countFiles(static_cast<const KArchiveDirectory *>(entry));
        }
    }
    return count;
}

void FilterKMailArchive::import()
{
Laurent Montel's avatar
Laurent Montel committed
175
    const QString archiveFile
176 177
        = QFileDialog::getOpenFileName(filterInfo()->parentWidget(), i18n("Select KMail Archive File to Import"),
                                       QString(), QStringLiteral("%1 (*.tar *.tar.gz *.tar.bz2 *.zip)").arg(i18n("KMail Archive Files ")));
178 179 180 181 182 183 184
    if (archiveFile.isEmpty()) {
        filterInfo()->alert(i18n("Please select an archive file that should be imported."));
        return;
    }
    importMails(archiveFile);
}

Laurent Montel's avatar
Laurent Montel committed
185
void FilterKMailArchive::importMails(const QString &archiveFile)
186
{
187 188 189 190
    if (archiveFile.isEmpty()) {
        filterInfo()->alert(i18n("No archive selected."));
        return;
    }
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
    filterInfo()->setFrom(archiveFile);

    QMimeDatabase db;
    QMimeType mimeType = db.mimeTypeForFile(archiveFile, QMimeDatabase::MatchExtension);
    typedef QSharedPointer<KArchive> KArchivePtr;
    KArchivePtr archive;
    if (!mimeType.globPatterns().filter(QStringLiteral("tar"), Qt::CaseInsensitive).isEmpty()) {
        archive = KArchivePtr(new KTar(archiveFile));
    } else if (!mimeType.globPatterns().filter(QStringLiteral("zip"), Qt::CaseInsensitive).isEmpty()) {
        archive = KArchivePtr(new KZip(archiveFile));
    } else {
        filterInfo()->alert(i18n("The file '%1' does not appear to be a valid archive.", archiveFile));
        return;
    }

    if (!archive->open(QIODevice::ReadOnly)) {
        filterInfo()->alert(i18n("Unable to open archive file '%1'", archiveFile));
        return;
    }

    filterInfo()->setOverall(0);
    filterInfo()->addInfoLogEntry(i18n("Counting files in archive..."));
    d->mTotalFiles = countFiles(archive->directory());

    if (importDirectory(archive->directory(), QString())) {
        filterInfo()->setOverall(100);
        filterInfo()->setCurrent(100);
        filterInfo()->addInfoLogEntry(i18n("Importing the archive file '%1' into the folder '%2' succeeded.",
219
                                           archiveFile, filterImporter()->topLevelFolder()));
220 221 222 223 224 225 226
        filterInfo()->addInfoLogEntry(i18np("1 message was imported.", "%1 messages were imported.",
                                            d->mFilesDone));
    } else {
        filterInfo()->addInfoLogEntry(i18n("Importing the archive failed."));
    }
    archive->close();
}