• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepim API Reference
  • KDE Home
  • Contact Us
 

mailcommon

  • sources
  • kde-4.12
  • kdepim
  • mailcommon
  • job
backupjob.cpp
Go to the documentation of this file.
1 /*
2 
3  Copyright (c) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License as
7  published by the Free Software Foundation; either version 2 of
8  the License or (at your option) version 3 or any later version
9  accepted by the membership of KDE e.V. (or its successor approved
10  by the membership of KDE e.V.), which shall act as a proxy
11  defined in Section 14 of version 3 of the license.
12 
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include "backupjob.h"
23 
24 #include <libkdepim/misc/broadcaststatus.h>
25 
26 #include <Akonadi/CollectionDeleteJob>
27 #include <Akonadi/CollectionFetchJob>
28 #include <Akonadi/CollectionFetchScope>
29 #include <Akonadi/ItemFetchJob>
30 #include <Akonadi/ItemFetchScope>
31 
32 #include <KMime/Message>
33 
34 #include <KLocale>
35 #include <KMessageBox>
36 #include <KTar>
37 #include <KZip>
38 #include <kio/global.h> //krazy:exclude=camelcase as there is no such
39 
40 #include <QFileInfo>
41 #include <QTimer>
42 
43 using namespace MailCommon;
44 static const mode_t archivePerms = S_IFREG | 0644;
45 
46 BackupJob::BackupJob( QWidget *parent )
47  : QObject( parent ),
48  mArchiveTime(QDateTime::currentDateTime().toTime_t()),
49  mArchiveType( Zip ),
50  mRootFolder( 0 ),
51  mArchive( 0 ),
52  mParentWidget( parent ),
53  mArchivedMessages( 0 ),
54  mArchivedSize( 0 ),
55  mProgressItem( 0 ),
56  mAborted( false ),
57  mDeleteFoldersAfterCompletion( false ),
58  mRecursive( true ),
59  mCurrentFolder( Akonadi::Collection() ),
60  mCurrentJob( 0 ),
61  mDisplayMessageBox(true)
62 {
63 }
64 
65 BackupJob::~BackupJob()
66 {
67  mPendingFolders.clear();
68  delete mArchive;
69  mArchive = 0;
70 }
71 
72 void BackupJob::setRootFolder( const Akonadi::Collection &rootFolder )
73 {
74  mRootFolder = rootFolder;
75 }
76 
77 void BackupJob::setRealPath(const QString &path)
78 {
79  mRealPath = path;
80 }
81 
82 void BackupJob::setSaveLocation( const KUrl &savePath )
83 {
84  mMailArchivePath = savePath;
85 }
86 
87 void BackupJob::setArchiveType( ArchiveType type )
88 {
89  mArchiveType = type;
90 }
91 
92 void BackupJob::setDeleteFoldersAfterCompletion( bool deleteThem )
93 {
94  mDeleteFoldersAfterCompletion = deleteThem;
95 }
96 
97 void BackupJob::setRecursive( bool recursive )
98 {
99  mRecursive = recursive;
100 }
101 
102 bool BackupJob::queueFolders( const Akonadi::Collection &root )
103 {
104  mPendingFolders.append( root );
105  if ( mRecursive ) {
106  // FIXME: Get rid of the exec()
107  // We could do a recursive CollectionFetchJob, but we only fetch the first level
108  // and then recurse manually. This is needed because a recursive fetch doesn't
109  // sort the collections the way we want. We need all first level children to be
110  // in the mPendingFolders list before all second level children, so that the
111  // directories for the first level are written before the directories in the
112  // second level, in the archive file.
113  Akonadi::CollectionFetchJob *job =
114  new Akonadi::CollectionFetchJob( root, Akonadi::CollectionFetchJob::FirstLevel );
115  job->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
116  job->exec();
117  if ( job->error() ) {
118  kWarning() << job->errorString();
119  abort( i18n( "Unable to retrieve folder list." ) );
120  return false;
121  }
122 
123  foreach ( const Akonadi::Collection &collection, job->collections() ) {
124  if ( !queueFolders( collection ) ) {
125  return false;
126  }
127  }
128  }
129  mAllFolders = mPendingFolders;
130  return true;
131 }
132 
133 bool BackupJob::hasChildren( const Akonadi::Collection &collection ) const
134 {
135  foreach ( const Akonadi::Collection &curCol, mAllFolders ) {
136  if ( collection == curCol.parentCollection() ) {
137  return true;
138  }
139  }
140  return false;
141 }
142 
143 void BackupJob::cancelJob()
144 {
145  abort( i18n( "The operation was canceled by the user." ) );
146 }
147 
148 void BackupJob::abort( const QString &errorMessage )
149 {
150  // We could be called this twice, since killing the current job below will
151  // cause the job to fail, and that will call abort()
152  if ( mAborted ) {
153  return;
154  }
155 
156  mAborted = true;
157  if ( mCurrentFolder.isValid() ) {
158  mCurrentFolder = Akonadi::Collection();
159  }
160 
161  if ( mArchive && mArchive->isOpen() ) {
162  mArchive->close();
163  }
164 
165  if ( mCurrentJob ) {
166  mCurrentJob->kill();
167  mCurrentJob = 0;
168  }
169 
170  if ( mProgressItem ) {
171  mProgressItem->setComplete();
172  mProgressItem = 0;
173  // The progressmanager will delete it
174  }
175  QString text = i18n( "Failed to archive the folder '%1'.", mRootFolder.name() );
176  text += QLatin1Char('\n') + errorMessage;
177  Q_EMIT error(text);
178  if (mDisplayMessageBox)
179  KMessageBox::sorry( mParentWidget, text, i18n( "Archiving failed" ) );
180  deleteLater();
181  // Clean up archive file here?
182 }
183 
184 void BackupJob::finish()
185 {
186  if ( mArchive->isOpen() ) {
187  if ( !mArchive->close() ) {
188  abort( i18n( "Unable to finalize the archive file." ) );
189  return;
190  }
191  }
192 
193  const QString archivingStr( i18n( "Archiving finished" ) );
194  KPIM::BroadcastStatus::instance()->setStatusMsg( archivingStr );
195 
196  if ( mProgressItem ) {
197  mProgressItem->setStatus( archivingStr );
198  mProgressItem->setComplete();
199  mProgressItem = 0;
200  }
201 
202  QFileInfo archiveFileInfo( mMailArchivePath.path() );
203  QString text = i18n( "Archiving folder '%1' successfully completed. "
204  "The archive was written to the file '%2'.",
205  mRealPath.isEmpty() ? mRootFolder.name() : mRealPath, mMailArchivePath.path() );
206  text += QLatin1Char('\n') + i18np( "1 message of size %2 was archived.",
207  "%1 messages with the total size of %2 were archived.",
208  mArchivedMessages, KIO::convertSize( mArchivedSize ) );
209  text += QLatin1Char('\n') + i18n( "The archive file has a size of %1.",
210  KIO::convertSize( archiveFileInfo.size() ) );
211  if (mDisplayMessageBox) {
212  KMessageBox::information( mParentWidget, text, i18n( "Archiving finished" ) );
213  }
214 
215  if ( mDeleteFoldersAfterCompletion ) {
216  // Some safety checks first...
217  if ( archiveFileInfo.exists() && ( mArchivedSize > 0 || mArchivedMessages == 0 ) ) {
218  // Sorry for any data loss!
219  new Akonadi::CollectionDeleteJob( mRootFolder );
220  }
221  }
222  Q_EMIT backupDone(text);
223  deleteLater();
224 }
225 
226 void BackupJob::archiveNextMessage()
227 {
228  if ( mAborted ) {
229  return;
230  }
231 
232  if ( mPendingMessages.isEmpty() ) {
233  kDebug() << "===> All messages done in folder " << mCurrentFolder.name();
234  archiveNextFolder();
235  return;
236  }
237 
238  Akonadi::Item item = mPendingMessages.front();
239  mPendingMessages.pop_front();
240  kDebug() << "Fetching item with ID" << item.id() << "for folder" << mCurrentFolder.name();
241 
242  mCurrentJob = new Akonadi::ItemFetchJob( item );
243  mCurrentJob->fetchScope().fetchFullPayload( true );
244  connect( mCurrentJob, SIGNAL(result(KJob*)),
245  this, SLOT(itemFetchJobResult(KJob*)) );
246 }
247 
248 void BackupJob::processMessage( const Akonadi::Item &item )
249 {
250  if ( mAborted ) {
251  return;
252  }
253 
254  const KMime::Message::Ptr message = item.payload<KMime::Message::Ptr>();
255  kDebug() << "Processing message with subject " << message->subject( false );
256  const QByteArray messageData = message->encodedContent();
257  const qint64 messageSize = messageData.size();
258  const QString messageName = QString::number( item.id() );
259  const QString fileName = pathForCollection( mCurrentFolder ) + QLatin1String("/cur/") + messageName;
260 
261  // PORT ME: user and group!
262  kDebug() << "AKONDI PORT: disabled code here!";
263  if ( !mArchive->writeFile( fileName, QLatin1String("user"), QLatin1String("group"), messageData, messageSize, archivePerms, mArchiveTime, mArchiveTime, mArchiveTime) ) {
264  abort( i18n( "Failed to write a message into the archive folder '%1'.",
265  mCurrentFolder.name() ) );
266  return;
267  }
268 
269  ++mArchivedMessages;
270  mArchivedSize += messageSize;
271 
272  // Use a singleshot timer, otherwise the job started in archiveNextMessage()
273  // will hang
274  QTimer::singleShot( 0, this, SLOT(archiveNextMessage()) );
275 }
276 
277 void BackupJob::itemFetchJobResult( KJob *job )
278 {
279  if ( mAborted ) {
280  return;
281  }
282 
283  Q_ASSERT( job == mCurrentJob );
284  mCurrentJob = 0;
285 
286  if ( job->error() ) {
287  Q_ASSERT( mCurrentFolder.isValid() );
288  kWarning() << job->errorString();
289  abort( i18n( "Downloading a message in folder '%1' failed.", mCurrentFolder.name() ) );
290  } else {
291  Akonadi::ItemFetchJob *fetchJob = dynamic_cast<Akonadi::ItemFetchJob*>( job );
292  Q_ASSERT( fetchJob );
293  Q_ASSERT( fetchJob->items().size() == 1 );
294  processMessage( fetchJob->items().first() );
295  }
296 }
297 
298 bool BackupJob::writeDirHelper( const QString &directoryPath )
299 {
300  // PORT ME: Correct user/group
301  kDebug() << "AKONDI PORT: Disabled code here!";
302  return mArchive->writeDir( directoryPath, QLatin1String("user"), QLatin1String("group"), 040755, mArchiveTime, mArchiveTime, mArchiveTime );
303 }
304 
305 QString BackupJob::collectionName( const Akonadi::Collection &collection ) const
306 {
307  foreach ( const Akonadi::Collection &curCol, mAllFolders ) {
308  if ( curCol == collection ) {
309  return curCol.name();
310  }
311  }
312  Q_ASSERT( false );
313  return QString();
314 }
315 
316 QString BackupJob::pathForCollection( const Akonadi::Collection &collection ) const
317 {
318  QString fullPath = collectionName( collection );
319  Akonadi::Collection curCol = collection.parentCollection();
320  if ( collection != mRootFolder ) {
321  Q_ASSERT( curCol.isValid() );
322  while ( curCol != mRootFolder ) {
323  fullPath.prepend( QLatin1Char('.') + collectionName( curCol ) + QString::fromLatin1(".directory/") );
324  curCol = curCol.parentCollection();
325  }
326  Q_ASSERT( curCol == mRootFolder );
327  fullPath.prepend( QLatin1Char('.') + collectionName( curCol ) + QString::fromLatin1(".directory/") );
328  }
329  return fullPath;
330 }
331 
332 QString BackupJob::subdirPathForCollection( const Akonadi::Collection &collection ) const
333 {
334  QString path = pathForCollection( collection );
335  const int parentDirEndIndex = path.lastIndexOf( collection.name() );
336  Q_ASSERT( parentDirEndIndex != -1 );
337  path = path.left( parentDirEndIndex );
338  path.append( QLatin1Char('.') + collection.name() + QLatin1String(".directory") );
339  return path;
340 }
341 
342 void BackupJob::archiveNextFolder()
343 {
344  if ( mAborted ) {
345  return;
346  }
347 
348  if ( mPendingFolders.isEmpty() ) {
349  finish();
350  return;
351  }
352 
353  mCurrentFolder = mPendingFolders.takeAt( 0 );
354  kDebug() << "===> Archiving next folder: " << mCurrentFolder.name();
355  const QString archivingStr( i18n( "Archiving folder %1", mCurrentFolder.name() ) );
356  if ( mProgressItem ) {
357  mProgressItem->setStatus( archivingStr );
358  }
359  KPIM::BroadcastStatus::instance()->setStatusMsg( archivingStr );
360 
361  const QString folderName = mCurrentFolder.name();
362  bool success = true;
363  if ( hasChildren( mCurrentFolder ) ) {
364  if ( !writeDirHelper( subdirPathForCollection( mCurrentFolder ) ) ) {
365  success = false;
366  }
367  }
368  if (success) {
369  if ( !writeDirHelper( pathForCollection( mCurrentFolder ) ) ) {
370  success = false;
371  } else if ( !writeDirHelper( pathForCollection( mCurrentFolder ) + QLatin1String("/cur") ) ) {
372  success = false;
373  } else if ( !writeDirHelper( pathForCollection( mCurrentFolder ) + QLatin1String("/new") ) ) {
374  success = false;
375  } else if ( !writeDirHelper( pathForCollection( mCurrentFolder ) + QLatin1String("/tmp") ) ) {
376  success = false;
377  }
378  }
379  if ( !success ) {
380  abort( i18n( "Unable to create folder structure for folder '%1' within archive file.",
381  mCurrentFolder.name() ) );
382  return;
383  }
384  Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( mCurrentFolder );
385  job->setProperty( "folderName", folderName );
386  connect( job, SIGNAL(result(KJob*)), SLOT(onArchiveNextFolderDone(KJob*)) );
387 }
388 
389 void BackupJob::onArchiveNextFolderDone( KJob *job )
390 {
391  if ( job->error() ) {
392  kWarning() << job->errorString();
393  abort( i18n( "Unable to get message list for folder %1.",
394  job->property( "folderName" ).toString() ) );
395  return;
396  }
397 
398  Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob*>( job );
399  mPendingMessages += fetchJob->items();
400  archiveNextMessage();
401 }
402 
403 void BackupJob::start()
404 {
405  Q_ASSERT( !mMailArchivePath.isEmpty() );
406  Q_ASSERT( mRootFolder.isValid() );
407 
408  if ( !queueFolders( mRootFolder ) ) {
409  return;
410  }
411 
412  switch ( mArchiveType ) {
413  case Zip:
414  {
415  KZip *zip = new KZip( mMailArchivePath.path() );
416  zip->setCompression( KZip::DeflateCompression );
417  mArchive = zip;
418  break;
419  }
420  case Tar:
421  {
422  mArchive = new KTar( mMailArchivePath.path(), QLatin1String("application/x-tar") );
423  break;
424  }
425  case TarGz:
426  {
427  mArchive = new KTar( mMailArchivePath.path(), QLatin1String("application/x-gzip") );
428  break;
429  }
430  case TarBz2:
431  {
432  mArchive = new KTar( mMailArchivePath.path(), QLatin1String("application/x-bzip2") );
433  break;
434  }
435  }
436 
437  kDebug() << "Starting backup.";
438  if ( !mArchive->open( QIODevice::WriteOnly ) ) {
439  abort( i18n( "Unable to open archive for writing." ) );
440  return;
441  }
442 
443  mProgressItem = KPIM::ProgressManager::createProgressItem(
444  QLatin1String("BackupJob"),
445  i18n( "Archiving" ),
446  QString(),
447  true );
448  mProgressItem->setUsesBusyIndicator( true );
449  connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
450  this, SLOT(cancelJob()) );
451 
452  archiveNextFolder();
453 }
454 
455 void BackupJob::setDisplayMessageBox(bool display)
456 {
457  mDisplayMessageBox = display;
458 }
459 
460 #include "backupjob.moc"
461 
MailCommon::BackupJob::Tar
Definition: backupjob.h:62
MailCommon::BackupJob::setDeleteFoldersAfterCompletion
void setDeleteFoldersAfterCompletion(bool deleteThem)
Definition: backupjob.cpp:92
MailCommon::BackupJob::backupDone
void backupDone(const QString &)
MailCommon::BackupJob::setRootFolder
void setRootFolder(const Akonadi::Collection &rootFolder)
Definition: backupjob.cpp:72
MailCommon::BackupJob::error
void error(const QString &)
MailCommon::BackupJob::TarGz
Definition: backupjob.h:64
text
const char * text
Definition: mdnadvicedialog.cpp:52
MailCommon::BackupJob::setDisplayMessageBox
void setDisplayMessageBox(bool display)
Definition: backupjob.cpp:455
QWidget
MailCommon::BackupJob::Zip
Definition: backupjob.h:61
QObject
MailCommon::BackupJob::setRealPath
void setRealPath(const QString &path)
Definition: backupjob.cpp:77
MailCommon::BackupJob::setArchiveType
void setArchiveType(ArchiveType type)
Definition: backupjob.cpp:87
backupjob.h
MailCommon::BackupJob::setRecursive
void setRecursive(bool recursive)
Definition: backupjob.cpp:97
MailCommon::BackupJob::ArchiveType
ArchiveType
Definition: backupjob.h:60
MailCommon::BackupJob::~BackupJob
~BackupJob()
Definition: backupjob.cpp:65
MailCommon::BackupJob::setSaveLocation
void setSaveLocation(const KUrl &savePath)
Definition: backupjob.cpp:82
MailCommon::BackupJob::TarBz2
Definition: backupjob.h:63
archivePerms
static const mode_t archivePerms
Definition: backupjob.cpp:44
MailCommon::BackupJob::BackupJob
BackupJob(QWidget *parent=0)
Definition: backupjob.cpp:46
MailCommon::BackupJob::start
void start()
Definition: backupjob.cpp:403
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:55:14 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

mailcommon

Skip menu "mailcommon"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal