kmail

importjob.cpp
1 /* Copyright 2009 Klarälvdalens Datakonsult AB
2 
3  This program is free software; you can redistribute it and/or
4  modify it under the terms of the GNU General Public License as
5  published by the Free Software Foundation; either version 2 of
6  the License or (at your option) version 3 or any later version
7  accepted by the membership of KDE e.V. (or its successor approved
8  by the membership of KDE e.V.), which shall act as a proxy
9  defined in Section 14 of version 3 of the license.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "importjob.h"
20 
21 #include "kmfolder.h"
22 #include "folderutil.h"
23 #include "kmfolderdir.h"
24 #include "kmfolderimap.h"
25 #include "imapjob.h"
26 
27 #include "progressmanager.h"
28 
29 #include <kdebug.h>
30 #include <kzip.h>
31 #include <ktar.h>
32 #include <tdelocale.h>
33 #include <tdemessagebox.h>
34 #include <kmimetype.h>
35 
36 #include <tqwidget.h>
37 #include <tqtimer.h>
38 #include <tqfile.h>
39 
40 using namespace KMail;
41 
42 KMail::ImportJob::ImportJob( TQWidget *parentWidget )
43  : TQObject( parentWidget ),
44  mArchive( 0 ),
45  mRootFolder( 0 ),
46  mParentWidget( parentWidget ),
47  mNumberOfImportedMessages( 0 ),
48  mCurrentFolder( 0 ),
49  mCurrentMessage( 0 ),
50  mCurrentMessageFile( 0 ),
51  mProgressItem( 0 ),
52  mAborted( false )
53 {
54 }
55 
56 KMail::ImportJob::~ImportJob()
57 {
58  if ( mArchive && mArchive->isOpened() ) {
59  mArchive->close();
60  }
61  delete mArchive;
62  mArchive = 0;
63 }
64 
65 void KMail::ImportJob::setFile( const KURL &archiveFile )
66 {
67  mArchiveFile = archiveFile;
68 }
69 
70 void KMail::ImportJob::setRootFolder( KMFolder *rootFolder )
71 {
72  mRootFolder = rootFolder;
73 }
74 
75 void KMail::ImportJob::finish()
76 {
77  kdDebug(5006) << "Finished import job." << endl;
78  mProgressItem->setComplete();
79  mProgressItem = 0;
80  TQString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." )
81  .arg( mArchiveFile.path() ).arg( mRootFolder->name() );
82  text += "\n" + i18n( "1 message was imported.", "%n messages were imported.", mNumberOfImportedMessages );
83  KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) );
84  deleteLater();
85 }
86 
87 void KMail::ImportJob::cancelJob()
88 {
89  abort( i18n( "The operation was canceled by the user." ) );
90 }
91 
92 void KMail::ImportJob::abort( const TQString &errorMessage )
93 {
94  if ( mAborted )
95  return;
96 
97  mAborted = true;
98  TQString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() );
99  text += "\n" + errorMessage;
100  if ( mProgressItem ) {
101  mProgressItem->setComplete();
102  mProgressItem = 0;
103  // The progressmanager will delete it
104  }
105  KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) );
106  deleteLater();
107 }
108 
109 KMFolder * KMail::ImportJob::createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions )
110 {
111  KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, TQString(),
112  KMFolderTypeMaildir );
113  if ( !newFolder ) {
114  abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) );
115  return 0;
116  }
117  else {
118  newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do
119  // that if really needed. We do it here so we can set the
120  // permissions
121  chmod( newFolder->location().latin1(), permissions | S_IXUSR );
122  chmod( newFolder->subdirLocation().latin1(), permissions | S_IXUSR );
123  // TODO: chown?
124  // TODO: what about subdirectories like "cur"?
125  return newFolder;
126  }
127 }
128 
129 void KMail::ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder )
130 {
131  const KArchiveDirectory *messageDir = dynamic_cast<const KArchiveDirectory*>( dir->entry( "cur" ) );
132  if ( messageDir ) {
133  Messages messagesToQueue;
134  messagesToQueue.parent = folder;
135  const TQStringList entries = messageDir->entries();
136  for ( uint i = 0; i < entries.size(); i++ ) {
137  const KArchiveEntry *entry = messageDir->entry( entries[i] );
138  Q_ASSERT( entry );
139  if ( entry->isDirectory() ) {
140  kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl;
141  }
142  else {
143  kdDebug(5006) << "Queueing message " << entry->name() << endl;
144  const KArchiveFile *file = static_cast<const KArchiveFile*>( entry );
145  messagesToQueue.files.append( file );
146  }
147  }
148  mQueuedMessages.append( messagesToQueue );
149  }
150  else {
151  kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl;
152  }
153 }
154 
155 void KMail::ImportJob::messageAdded()
156 {
157  mNumberOfImportedMessages++;
158  if ( mCurrentFolder->folderType() == KMFolderTypeMaildir ||
159  mCurrentFolder->folderType() == KMFolderTypeCachedImap ) {
160  const TQString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName();
161  // TODO: what if the file is not in the "cur" subdirectory?
162  if ( TQFile::exists( messageFile ) ) {
163  chmod( messageFile.latin1(), mCurrentMessageFile->permissions() );
164  // TODO: changing user/group he requires a bit more work, requires converting the strings
165  // to uid_t and gid_t
166  //getpwnam()
167  //chown( messageFile,
168  }
169  else {
170  kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl;
171  }
172  }
173  // TODO: Else?
174 
175  mCurrentMessage = 0;
176  mCurrentMessageFile = 0;
177  TQTimer::singleShot( 0, this, TQ_SLOT( importNextMessage() ) );
178 }
179 
180 void KMail::ImportJob::importNextMessage()
181 {
182  if ( mAborted )
183  return;
184 
185  if ( mQueuedMessages.isEmpty() ) {
186  kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl;
187  if ( mCurrentFolder ) {
188  mCurrentFolder->close( "ImportJob" );
189  }
190  mCurrentFolder = 0;
191  importNextDirectory();
192  return;
193  }
194 
195  Messages &messages = mQueuedMessages.front();
196  if ( messages.files.isEmpty() ) {
197  mQueuedMessages.pop_front();
198  importNextMessage();
199  return;
200  }
201 
202  KMFolder *folder = messages.parent;
203  if ( folder != mCurrentFolder ) {
204  kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl;
205  if ( mCurrentFolder ) {
206  mCurrentFolder->close( "ImportJob" );
207  }
208  mCurrentFolder = folder;
209  if ( mCurrentFolder->open( "ImportJob" ) != 0 ) {
210  abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) );
211  return;
212  }
213  kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl;
214  mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) );
215  }
216 
217  mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );
218 
219  mCurrentMessageFile = messages.files.first();
220  Q_ASSERT( mCurrentMessageFile );
221  messages.files.removeFirst();
222 
223  mCurrentMessage = new KMMessage();
224  mCurrentMessage->fromByteArray( mCurrentMessageFile->data(), true /* setStatus */ );
225  int retIndex;
226 
227  // If this is not an IMAP folder, we can add the message directly. Otherwise, the whole thing is
228  // async, for online IMAP. While addMsg() fakes a sync call, we rather do it the async way here
229  // ourselves, as otherwise the whole thing gets pretty much messed up with regards to folder
230  // refcounting. Furthermore, the completion dialog would be shown before the messages are actually
231  // uploaded.
232  if ( mCurrentFolder->folderType() != KMFolderTypeImap ) {
233  if ( mCurrentFolder->addMsg( mCurrentMessage, &retIndex ) != 0 ) {
234  abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) );
235  return;
236  }
237  messageAdded();
238  }
239  else {
240  ImapJob *imapJob = new ImapJob( mCurrentMessage, ImapJob::tPutMessage,
241  dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() ) );
242  connect( imapJob, TQ_SIGNAL(result(KMail::FolderJob*)),
243  TQ_SLOT(messagePutResult(KMail::FolderJob*)) );
244  imapJob->start();
245  }
246 }
247 
248 void KMail::ImportJob::messagePutResult( KMail::FolderJob *job )
249 {
250  if ( mAborted )
251  return;
252 
253  if ( job->error() ) {
254  abort( i18n( "Failed to upload a message to the IMAP server." ) );
255  return;
256  } else {
257 
258  KMFolderImap *imap = dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() );
259  Q_ASSERT( imap );
260 
261  // Ok, we uploaded the message, but we still need to add it to the folder. Use addMsgQuiet(),
262  // otherwise it will be uploaded again.
263  imap->addMsgQuiet( mCurrentMessage );
264  messageAdded();
265  }
266 }
267 
268 // Input: .inbox.directory
269 // Output: inbox
270 // Can also return an empty string if this is no valid dir name
271 static TQString folderNameForDirectoryName( const TQString &dirName )
272 {
273  Q_ASSERT( dirName.startsWith( "." ) );
274  const TQString end = ".directory";
275  const int expectedIndex = dirName.length() - end.length();
276  if ( dirName.lower().find( end ) != expectedIndex )
277  return TQString();
278  TQString returnName = dirName.left( dirName.length() - end.length() );
279  returnName = returnName.right( returnName.length() - 1 );
280  return returnName;
281 }
282 
283 KMFolder* KMail::ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const TQString &subFolderName,
284  mode_t subFolderPermissions )
285 {
286  if ( !parentFolder->createChildFolder() ) {
287  abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) );
288  return 0;
289  }
290 
291  KMFolder *subFolder = 0;
292  subFolder = dynamic_cast<KMFolder*>( parentFolder->child()->hasNamedFolder( subFolderName ) );
293 
294  if ( !subFolder ) {
295  subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions );
296  }
297  return subFolder;
298 }
299 
300 void KMail::ImportJob::importNextDirectory()
301 {
302  if ( mAborted )
303  return;
304 
305  if ( mQueuedDirectories.isEmpty() ) {
306  finish();
307  return;
308  }
309 
310  Folder folder = mQueuedDirectories.first();
311  KMFolder *currentFolder = folder.parent;
312  mQueuedDirectories.pop_front();
313  kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl;
314 
315  TQStringList entries = folder.archiveDir->entries();
316  for ( uint i = 0; i < entries.size(); i++ ) {
317  const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] );
318  Q_ASSERT( entry );
319  kdDebug(5006) << "Queueing entry " << entry->name() << endl;
320  if ( entry->isDirectory() ) {
321  const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>( entry );
322  if ( !dir->name().startsWith( "." ) ) {
323 
324  kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl;
325  KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() );
326  if ( !subFolder )
327  return;
328 
329  enqueueMessages( dir, subFolder );
330  }
331 
332  // Entry starts with a dot, so we assume it is a subdirectory
333  else {
334 
335  const TQString folderName = folderNameForDirectoryName( entry->name() );
336  if ( folderName.isEmpty() ) {
337  abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) );
338  return;
339  }
340  KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() );
341  if ( !subFolder )
342  return;
343 
344  Folder newFolder;
345  newFolder.archiveDir = dir;
346  newFolder.parent = subFolder;
347  kdDebug(5006) << "Enqueueing directory " << entry->name() << endl;
348  mQueuedDirectories.push_back( newFolder );
349  }
350  }
351  }
352 
353  importNextMessage();
354 }
355 
356 // TODO:
357 // BUGS:
358 // Online IMAP can fail spectacular, for example when cancelling upload
359 // Online IMAP: Inform that messages are still being uploaded on finish()!
360 void KMail::ImportJob::start()
361 {
362  Q_ASSERT( mRootFolder );
363  Q_ASSERT( mArchiveFile.isValid() );
364 
365  KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ );
366  if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() )
367  mArchive = new KTar( mArchiveFile.path() );
368  else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() )
369  mArchive = new KZip( mArchiveFile.path() );
370  else {
371  abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) );
372  return;
373  }
374 
375  if ( !mArchive->open( IO_ReadOnly ) ) {
376  abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) );
377  return;
378  }
379 
380  mProgressItem = KPIM::ProgressManager::createProgressItem(
381  "ImportJob",
382  i18n( "Importing Archive" ),
383  TQString(),
384  true );
385  mProgressItem->setUsesBusyIndicator( true );
386  connect( mProgressItem, TQ_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
387  this, TQ_SLOT(cancelJob()) );
388 
389  Folder nextFolder;
390  nextFolder.archiveDir = mArchive->directory();
391  nextFolder.parent = mRootFolder;
392  mQueuedDirectories.push_back( nextFolder );
393  importNextDirectory();
394 }
395 
396 #include "importjob.moc"
virtual KMFolderNode * hasNamedFolder(const TQString &name)
Returns folder with given name or zero if it does not exist.
Mail folder.
Definition: kmfolder.h:69
TQString subdirLocation() const
Returns full path to sub directory file.
Definition: kmfolder.cpp:253
KMFolderDir * child() const
Returns the folder directory associated with this node or 0 if no such directory exists.
Definition: kmfolder.h:157
void close(const char *owner, bool force=false)
Close folder.
Definition: kmfolder.cpp:489
KMFolderDir * createChildFolder()
Create a child folder directory and associates it with this folder.
Definition: kmfolder.cpp:264
TQString location() const
Returns full path to folder file.
Definition: kmfolder.cpp:243
This is a Mime Message.
Definition: kmmessage.h:68
folderdiaquotatab.h
Definition: aboutdata.cpp:40