10#include "mailcommon_debug.h"
11#include <Akonadi/CollectionDeleteJob>
12#include <Akonadi/CollectionFetchJob>
13#include <Akonadi/CollectionFetchScope>
14#include <Akonadi/ItemFetchJob>
15#include <Akonadi/ItemFetchScope>
16#include <PimCommon/BroadcastStatus>
18#include <KMime/Message>
21#include <KLocalizedString>
30static const mode_t archivePerms = S_IFREG | 0644;
32BackupJob::BackupJob(
QWidget *parent)
34 , mArchiveTime(
QDateTime::currentDateTime())
36 , mParentWidget(parent)
37 , mCurrentFolder(
Akonadi::Collection())
41BackupJob::~BackupJob()
43 mPendingFolders.clear();
48void BackupJob::setRootFolder(
const Akonadi::Collection &rootFolder)
50 mRootFolder = rootFolder;
53void BackupJob::setRealPath(
const QString &path)
58void BackupJob::setSaveLocation(
const QUrl &savePath)
60 mMailArchivePath = savePath;
63void BackupJob::setArchiveType(ArchiveType type)
68void BackupJob::setDeleteFoldersAfterCompletion(
bool deleteThem)
70 mDeleteFoldersAfterCompletion = deleteThem;
73void BackupJob::setRecursive(
bool recursive)
75 mRecursive = recursive;
78bool BackupJob::queueFolders(
const Akonadi::Collection &root)
80 mPendingFolders.append(root);
93 qCWarning(MAILCOMMON_LOG) << job->errorString();
94 abort(
i18n(
"Unable to retrieve folder list."));
99 for (
const Akonadi::Collection &collection : lstCols) {
100 if (!queueFolders(collection)) {
105 mAllFolders = mPendingFolders;
109bool BackupJob::hasChildren(
const Akonadi::Collection &collection)
const
111 for (
const Akonadi::Collection &curCol : std::as_const(mAllFolders)) {
112 if (collection == curCol.parentCollection()) {
119void BackupJob::cancelJob()
121 abort(
i18n(
"The operation was canceled by the user."));
124void BackupJob::abort(
const QString &errorMessage)
133 if (mCurrentFolder.isValid()) {
134 mCurrentFolder = Akonadi::Collection();
137 if (mArchive && mArchive->isOpen()) {
143 mCurrentJob =
nullptr;
147 mProgressItem->setComplete();
148 mProgressItem =
nullptr;
151 QString text =
i18n(
"Failed to archive the folder '%1'.", mRootFolder.name());
154 if (mDisplayMessageBox) {
161void BackupJob::finish()
163 if (mArchive->isOpen()) {
164 if (!mArchive->close()) {
165 abort(
i18n(
"Unable to finalize the archive file."));
170 const QString archivingStr(
i18n(
"Archiving finished"));
171 PimCommon::BroadcastStatus::instance()->setStatusMsg(archivingStr);
174 mProgressItem->setStatus(archivingStr);
175 mProgressItem->setComplete();
176 mProgressItem =
nullptr;
179 const QFileInfo archiveFileInfo(mMailArchivePath.path());
181 "Archiving folder '%1' successfully completed. "
182 "The archive was written to the file '%2'.",
183 mRealPath.isEmpty() ? mRootFolder.name() : mRealPath,
184 mMailArchivePath.path());
186 text += QLatin1Char(
'\n')
187 +
i18np(
"1 message of size %2 was archived.",
188 "%1 messages with the total size of %2 were archived.",
191 text += QLatin1Char(
'\n') +
i18n(
"The archive file has a size of %1.", format.
formatByteSize(archiveFileInfo.size()));
192 if (mDisplayMessageBox) {
196 if (mDeleteFoldersAfterCompletion) {
198 if (archiveFileInfo.exists() && (mArchivedSize > 0 || mArchivedMessages == 0)) {
200 new Akonadi::CollectionDeleteJob(mRootFolder);
207void BackupJob::archiveNextMessage()
213 if (mPendingMessages.isEmpty()) {
214 qCDebug(MAILCOMMON_LOG) <<
"===> All messages done in folder " << mCurrentFolder.name();
219 const Akonadi::Item item = mPendingMessages.takeFirst();
220 qCDebug(MAILCOMMON_LOG) <<
"Fetching item with ID" << item.
id() <<
"for folder" << mCurrentFolder.name();
222 mCurrentJob =
new Akonadi::ItemFetchJob(item);
223 mCurrentJob->fetchScope().fetchFullPayload(
true);
224 connect(mCurrentJob, &Akonadi::ItemFetchJob::result,
this, &BackupJob::itemFetchJobResult);
227void BackupJob::processMessage(
const Akonadi::Item &item)
234 qCDebug(MAILCOMMON_LOG) <<
"Processing message with subject " << message->subject(
false);
235 const QByteArray messageData = message->encodedContent();
236 const qint64 messageSize = messageData.
size();
238 const QString fileName = pathForCollection(mCurrentFolder) + QLatin1StringView(
"/cur/") + messageName;
241 qCDebug(MAILCOMMON_LOG) <<
"AKONDI PORT: disabled code here!";
242 if (!mArchive->writeFile(fileName, messageData, archivePerms, QStringLiteral(
"user"), QStringLiteral(
"group"), mArchiveTime, mArchiveTime, mArchiveTime)) {
243 abort(
i18n(
"Failed to write a message into the archive folder '%1'.", mCurrentFolder.name()));
248 mArchivedSize += messageSize;
255void BackupJob::itemFetchJobResult(KJob *job)
261 Q_ASSERT(job == mCurrentJob);
262 mCurrentJob =
nullptr;
265 Q_ASSERT(mCurrentFolder.isValid());
267 abort(
i18n(
"Downloading a message in folder '%1' failed.", mCurrentFolder.name()));
271 Q_ASSERT(fetchJob->items().size() == 1);
272 processMessage(fetchJob->items().constFirst());
276bool BackupJob::writeDirHelper(
const QString &directoryPath)
279 qCDebug(MAILCOMMON_LOG) <<
"AKONDI PORT: Disabled code here!";
280 return mArchive->writeDir(directoryPath, QStringLiteral(
"user"), QStringLiteral(
"group"), 040755, mArchiveTime, mArchiveTime, mArchiveTime);
283QString BackupJob::collectionName(
const Akonadi::Collection &collection)
const
285 for (
const Akonadi::Collection &curCol : std::as_const(mAllFolders)) {
286 if (curCol == collection) {
287 return curCol.name();
294QString BackupJob::pathForCollection(
const Akonadi::Collection &collection)
const
296 QString fullPath = collectionName(collection);
298 if (collection != mRootFolder) {
300 while (curCol != mRootFolder) {
301 fullPath.
prepend(QLatin1Char(
'.') + collectionName(curCol) + QLatin1StringView(
".directory/"));
304 Q_ASSERT(curCol == mRootFolder);
305 fullPath.
prepend(QLatin1Char(
'.') + collectionName(curCol) + QLatin1StringView(
".directory/"));
310QString BackupJob::subdirPathForCollection(
const Akonadi::Collection &collection)
const
312 QString
path = pathForCollection(collection);
314 Q_ASSERT(parentDirEndIndex != -1);
316 path.
append(QLatin1Char(
'.') + collection.
name() + QLatin1StringView(
".directory"));
320void BackupJob::archiveNextFolder()
326 if (mPendingFolders.isEmpty()) {
331 mCurrentFolder = mPendingFolders.takeAt(0);
332 qCDebug(MAILCOMMON_LOG) <<
"===> Archiving next folder: " << mCurrentFolder.name();
333 const QString archivingStr(
i18n(
"Archiving folder %1", mCurrentFolder.name()));
335 mProgressItem->setStatus(archivingStr);
337 PimCommon::BroadcastStatus::instance()->setStatusMsg(archivingStr);
339 const QString folderName = mCurrentFolder.name();
341 if (hasChildren(mCurrentFolder)) {
342 if (!writeDirHelper(subdirPathForCollection(mCurrentFolder))) {
347 if (!writeDirHelper(pathForCollection(mCurrentFolder))) {
349 }
else if (!writeDirHelper(pathForCollection(mCurrentFolder) + QLatin1StringView(
"/cur"))) {
351 }
else if (!writeDirHelper(pathForCollection(mCurrentFolder) + QLatin1StringView(
"/new"))) {
353 }
else if (!writeDirHelper(pathForCollection(mCurrentFolder) + QLatin1StringView(
"/tmp"))) {
358 abort(
i18n(
"Unable to create folder structure for folder '%1' within archive file.", mCurrentFolder.name()));
361 auto job =
new Akonadi::ItemFetchJob(mCurrentFolder);
363 connect(job, &Akonadi::ItemFetchJob::result,
this, &BackupJob::onArchiveNextFolderDone);
366void BackupJob::onArchiveNextFolderDone(KJob *job)
375 mPendingMessages += fetchJob->items();
376 archiveNextMessage();
379void BackupJob::start()
381 Q_ASSERT(!mMailArchivePath.isEmpty());
382 Q_ASSERT(mRootFolder.isValid());
384 if (!queueFolders(mRootFolder)) {
388 switch (mArchiveType) {
390 KZip *zip =
new KZip(mMailArchivePath.path());
396 mArchive =
new KTar(mMailArchivePath.path(), QStringLiteral(
"application/x-tar"));
399 mArchive =
new KTar(mMailArchivePath.path(), QStringLiteral(
"application/x-gzip"));
402 mArchive =
new KTar(mMailArchivePath.path(), QStringLiteral(
"application/x-bzip2"));
406 qCDebug(MAILCOMMON_LOG) <<
"Starting backup.";
408 abort(
i18n(
"Unable to open archive for writing."));
413 mProgressItem->setUsesBusyIndicator(
true);
419void BackupJob::setDisplayMessageBox(
bool display)
421 mDisplayMessageBox = display;
424#include "moc_backupjob.cpp"
Collection & parentCollection()
virtual QString errorString() const
QSharedPointer< Message > Ptr
void progressItemCanceled(KPIM::ProgressItem *)
static ProgressItem * createProgressItem(const QString &id, const QString &label, const QString &status=QString(), bool canBeCanceled=true, KPIM::ProgressItem::CryptoStatus cryptoStatus=KPIM::ProgressItem::Unencrypted)
void setCompression(Compression c)
Q_SCRIPTABLE Q_NOREPLY void abort()
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...)
Type type(const QSqlDatabase &db)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
QString path(const QString &relativePath)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
bool setProperty(const char *name, QVariant &&value)
QString & append(QChar ch)
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
void truncate(qsizetype position)
QString toString() const const