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
40using namespace KMail;
41
42KMail::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
56KMail::ImportJob::~ImportJob()
57{
58 if ( mArchive && mArchive->isOpened() ) {
59 mArchive->close();
60 }
61 delete mArchive;
62 mArchive = 0;
63}
64
65void KMail::ImportJob::setFile( const KURL &archiveFile )
66{
67 mArchiveFile = archiveFile;
68}
69
70void KMail::ImportJob::setRootFolder( KMFolder *rootFolder )
71{
72 mRootFolder = rootFolder;
73}
74
75void 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
87void KMail::ImportJob::cancelJob()
88{
89 abort( i18n( "The operation was canceled by the user." ) );
90}
91
92void 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
109KMFolder * 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
129void 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
155void 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
180void 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
248void 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
271static 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
283KMFolder* 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
300void 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()!
360void 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
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
KMFolderDir * child() const
Returns the folder directory associated with this node or 0 if no such directory exists.
Definition: kmfolder.h:157
This is a Mime Message.
Definition: kmmessage.h:68
folderdiaquotatab.h
Definition: aboutdata.cpp:40