Don't offer to restart a Running (=busy) agent, it won't work.
[kdepim.git] / messageviewer / util.cpp
blobd7d07e59f142f7bd4f5d8cb0dfc249055daceca4
1 /*******************************************************************************
2 **
3 ** Filename : util
4 ** Created on : 03 April, 2005
5 ** Copyright : (c) 2005 Till Adam
6 ** Email : <adam@kde.org>
7 **
8 *******************************************************************************/
10 /*******************************************************************************
12 ** This program is free software; you can redistribute it and/or modify
13 ** it under the terms of the GNU General Public License as published by
14 ** the Free Software Foundation; either version 2 of the License, or
15 ** (at your option) any later version.
17 ** It is distributed in the hope that it will be useful, but
18 ** WITHOUT ANY WARRANTY; without even the implied warranty of
19 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ** General Public License for more details.
22 ** You should have received a copy of the GNU General Public License
23 ** along with this program; if not, write to the Free Software
24 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 ** In addition, as a special exception, the copyright holders give
27 ** permission to link the code of this program with any edition of
28 ** the Qt library by Trolltech AS, Norway (or with modified versions
29 ** of Qt that use the same license as Qt), and distribute linked
30 ** combinations including the two. You must obey the GNU General
31 ** Public License in all respects for all of the code used other than
32 ** Qt. If you modify this file, you may extend this exception to
33 ** your version of the file, but you are not obligated to do so. If
34 ** you do not wish to do so, delete this exception statement from
35 ** your version.
37 *******************************************************************************/
38 #include <config-messageviewer.h>
40 #include "util.h"
42 #include "iconnamecache.h"
43 #include "nodehelper.h"
44 #include "renamefiledialog.h"
46 #include "messagecore/globalsettings.h"
47 #include "messagecore/nodehelper.h"
48 #include "messagecore/stringutil.h"
50 #include <akonadi/item.h>
53 #include <kmbox/mbox.h>
55 #include <KMime/Message>
57 #include <kcharsets.h>
58 #include <KFileDialog>
59 #include <kglobal.h>
60 #include <klocale.h>
61 #include <kmessagebox.h>
62 #include <kio/netaccess.h>
63 #include <kdebug.h>
64 #include <KMimeType>
65 #include <KTemporaryFile>
67 #include <QTextCodec>
68 #include <QWidget>
70 using namespace MessageViewer;
72 bool Util::checkOverwrite( const KUrl &url, QWidget *w )
74 if ( KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, w ) ) {
75 if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
77 i18n( "A file named \"%1\" already exists. "
78 "Are you sure you want to overwrite it?", url.prettyUrl() ),
79 i18n( "Overwrite File?" ),
80 KStandardGuiItem::overwrite() ) )
81 return false;
83 return true;
86 QString Util::fileNameForMimetype( const QString &mimeType, int iconSize,
87 const QString &fallbackFileName1,
88 const QString &fallbackFileName2 )
90 QString fileName;
91 KMimeType::Ptr mime = KMimeType::mimeType( mimeType, KMimeType::ResolveAliases );
92 if ( mime ) {
93 fileName = mime->iconName();
94 } else {
95 fileName = QLatin1String( "unknown" );
96 kWarning() << "unknown mimetype" << mimeType;
99 if ( fileName.isEmpty() )
101 fileName = fallbackFileName1;
102 if ( fileName.isEmpty() )
103 fileName = fallbackFileName2;
104 if ( !fileName.isEmpty() ) {
105 fileName = KMimeType::findByPath( "/tmp/" + fileName, 0, true )->iconName();
109 return IconNameCache::instance()->iconPath( fileName, iconSize );
112 #if defined Q_WS_WIN || defined Q_WS_MACX
113 #include <QDesktopServices>
114 #endif
116 bool Util::handleUrlWithQDesktopServices( const KUrl& url )
118 #if defined Q_WS_WIN || defined Q_WS_MACX
119 QDesktopServices::openUrl( url );
120 return true;
121 #else
122 Q_UNUSED( url );
123 return false;
124 #endif
127 QList<KMime::Content*> Util::allContents( const KMime::Content *message )
129 KMime::Content::List result;
130 KMime::Content *child = MessageCore::NodeHelper::firstChild( message );
131 if ( child ) {
132 result += child;
133 result += allContents( child );
135 KMime::Content *next = MessageCore::NodeHelper::nextSibling( message );
136 if ( next ) {
137 result += next;
138 result += allContents( next );
141 return result;
144 QList<KMime::Content*> Util::extractAttachments( const KMime::Message *message )
146 const KMime::Content::List contents = allContents( message );
147 KMime::Content::List result;
148 for ( KMime::Content::List::const_iterator it = contents.constBegin();
149 it != contents.constEnd(); ) {
150 KMime::Content* content = *it;
151 if ( content->contentDisposition()->filename().trimmed().isEmpty() &&
152 ( content->contentType()->name().trimmed().isEmpty() ||
153 content == message ) ) {
154 ++it;
155 } else {
156 result <<( *it );
157 ++it;
161 return result;
164 bool Util::saveContents( QWidget *parent, const QList<KMime::Content*> &contents )
166 KUrl url, dirUrl;
167 if ( contents.count() > 1 ) {
168 // get the dir
169 dirUrl = KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
170 parent,
171 i18n( "Save Attachments To" ) );
172 if ( !dirUrl.isValid() ) {
173 return false;
176 // we may not get a slash-terminated url out of KFileDialog
177 dirUrl.adjustPath( KUrl::AddTrailingSlash );
179 else {
180 // only one item, get the desired filename
181 KMime::Content *content = contents.first();
182 QString fileName = NodeHelper::fileName( content );
183 fileName = MessageCore::StringUtil::cleanFileName( fileName );
184 if ( fileName.isEmpty() ) {
185 fileName = i18nc( "filename for an unnamed attachment", "attachment.1" );
187 url = KFileDialog::getSaveUrl( KUrl( "kfiledialog:///saveAttachment/" + fileName ),
188 QString(),
189 parent,
190 i18n( "Save Attachment" ) );
191 if ( url.isEmpty() ) {
192 return false;
196 QMap< QString, int > renameNumbering;
198 bool globalResult = true;
199 int unnamedAtmCount = 0;
200 MessageViewer::RenameFileDialog::RenameFileDialogResult result = MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE;
201 foreach( KMime::Content *content, contents ) {
202 KUrl curUrl;
203 if ( !dirUrl.isEmpty() ) {
204 curUrl = dirUrl;
205 QString fileName = MessageViewer::NodeHelper::fileName( content );
206 fileName = MessageCore::StringUtil::cleanFileName( fileName );
207 if ( fileName.isEmpty() ) {
208 ++unnamedAtmCount;
209 fileName = i18nc( "filename for the %1-th unnamed attachment",
210 "attachment.%1", unnamedAtmCount );
212 curUrl.setFileName( fileName );
213 } else {
214 curUrl = url;
217 if ( !curUrl.isEmpty() ) {
219 // Rename the file if we have already saved one with the same name:
220 // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
221 QString origFile = curUrl.fileName();
222 QString file = origFile;
224 while ( renameNumbering.contains(file) ) {
225 file = origFile;
226 int num = renameNumbering[file] + 1;
227 int dotIdx = file.lastIndexOf('.');
228 file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
230 curUrl.setFileName(file);
232 // Increment the counter for both the old and the new filename
233 if ( !renameNumbering.contains(origFile))
234 renameNumbering[origFile] = 1;
235 else
236 renameNumbering[origFile]++;
238 if ( file != origFile ) {
239 if ( !renameNumbering.contains(file))
240 renameNumbering[file] = 1;
241 else
242 renameNumbering[file]++;
246 if( !(result == MessageViewer::RenameFileDialog::RENAMEFILE_OVERWRITEALL ||
247 result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ))
249 if ( KIO::NetAccess::exists( curUrl, KIO::NetAccess::DestinationSide, parent ) ) {
250 if ( contents.count() == 1 ) {
251 RenameFileDialog *dlg = new RenameFileDialog(curUrl,false, parent);
252 result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
253 if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE )
255 delete dlg;
256 continue;
258 else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
260 curUrl = dlg->newName();
262 delete dlg;
264 else {
265 RenameFileDialog *dlg = new RenameFileDialog(curUrl,true, parent);
266 result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
268 if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE ||
269 result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL )
271 delete dlg;
272 continue;
274 else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
276 curUrl = dlg->newName();
278 delete dlg;
282 // save
283 if( result != MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ) {
284 const bool result = saveContent( parent, content, curUrl );
285 if ( !result )
286 globalResult = result;
291 return globalResult;
294 bool Util::saveContent( QWidget *parent, KMime::Content* content, const KUrl& url )
296 // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
297 // reading out the encryption/signature state will not work at all.
298 // Then, topLevel() will not work for attachments that are inside encrypted parts.
299 // What should actually be done is either passing in an ObjectTreeParser that has already
300 // parsed the message, or creating an OTP here (which would have the downside that the
301 // password dialog for decrypting messages is shown twice)
302 #if 0 // totally broken
303 KMime::Content *topContent = content->topLevel();
304 MessageViewer::NodeHelper *mNodeHelper = new MessageViewer::NodeHelper;
305 bool bSaveEncrypted = false;
306 bool bEncryptedParts = mNodeHelper->encryptionState( content ) != MessageViewer::KMMsgNotEncrypted;
307 if( bEncryptedParts )
308 if( KMessageBox::questionYesNo( parent,
309 i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
310 url.fileName() ),
311 i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
312 KMessageBox::Yes )
313 bSaveEncrypted = true;
315 bool bSaveWithSig = true;
316 if(mNodeHelper->signatureState( content ) != MessageViewer::KMMsgNotSigned )
317 if( KMessageBox::questionYesNo( parent,
318 i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?",
319 url.fileName() ),
320 i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
321 KMessageBox::Yes )
322 bSaveWithSig = false;
324 QByteArray data;
325 if( bSaveEncrypted || !bEncryptedParts) {
326 KMime::Content *dataNode = content;
327 QByteArray rawDecryptedBody;
328 bool gotRawDecryptedBody = false;
329 if ( !bSaveWithSig ) {
330 if ( topContent->contentType()->mimeType() == "multipart/signed" ) {
331 // carefully look for the part that is *not* the signature part:
332 if ( ObjectTreeParser::findType( topContent, "application/pgp-signature", true, false ) ) {
333 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pgp-signature", true, false );
334 } else if ( ObjectTreeParser::findType( topContent, "application/pkcs7-mime" , true, false ) ) {
335 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pkcs7-mime", true, false );
336 } else {
337 dataNode = ObjectTreeParser::findTypeNot( topContent, "multipart", "", true, false );
339 } else {
340 EmptySource emptySource;
341 ObjectTreeParser otp( &emptySource, 0, 0, false, false );
343 // process this node and all it's siblings and descendants
344 mNodeHelper->setNodeUnprocessed( dataNode, true );
345 otp.parseObjectTree( dataNode );
347 rawDecryptedBody = otp.rawDecryptedBody();
348 gotRawDecryptedBody = true;
351 QByteArray cstr = gotRawDecryptedBody
352 ? rawDecryptedBody
353 : dataNode->decodedContent();
354 data = KMime::CRLFtoLF( cstr );
356 #else
357 const QByteArray data = content->decodedContent();
358 kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
359 #endif
360 QDataStream ds;
361 QFile file;
362 KTemporaryFile tf;
363 if ( url.isLocalFile() )
365 // save directly
366 file.setFileName( url.toLocalFile() );
367 if ( !file.open( QIODevice::WriteOnly ) )
369 KMessageBox::error( parent,
370 i18nc( "1 = file name, 2 = error string",
371 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
372 file.fileName(),
373 file.errorString() ),
374 i18n( "Error saving attachment" ) );
375 return false;
378 const int permissions = MessageViewer::Util::getWritePermissions();
379 if ( permissions >= 0 )
380 fchmod( file.handle(), permissions );
382 ds.setDevice( &file );
383 } else
385 // tmp file for upload
386 tf.open();
387 ds.setDevice( &tf );
390 const int bytesWritten = ds.writeRawData( data.data(), data.size() );
391 if ( bytesWritten != data.size() ) {
392 QFile *f = static_cast<QFile *>( ds.device() );
393 KMessageBox::error( parent,
394 i18nc( "1 = file name, 2 = error string",
395 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
396 f->fileName(),
397 f->errorString() ),
398 i18n( "Error saving attachment" ) );
399 // Remove the newly created empty or partial file
400 f->remove();
401 return false;
404 if ( !url.isLocalFile() )
406 // QTemporaryFile::fileName() is only defined while the file is open
407 QString tfName = tf.fileName();
408 tf.close();
409 if ( !KIO::NetAccess::upload( tfName, url, parent ) )
411 KMessageBox::error( parent,
412 i18nc( "1 = file name, 2 = error string",
413 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
414 url.prettyUrl(),
415 KIO::NetAccess::lastErrorString() ),
416 i18n( "Error saving attachment" ) );
417 return false;
420 else
421 file.close();
423 #if 0
424 mNodeHelper->removeTempFiles();
425 delete mNodeHelper;
426 #endif
427 return true;
431 int Util::getWritePermissions()
433 // #79685, #232001 by default use the umask the user defined, but let it be configurable
434 if ( MessageCore::GlobalSettings::self()->disregardUmask() ) {
435 return S_IRUSR | S_IWUSR;
436 } else {
437 return -1;
441 bool Util::saveAttachments( const KMime::Content::List& contents, QWidget *parent )
443 if ( contents.isEmpty() ) {
444 KMessageBox::information( parent, i18n( "Found no attachments to save." ) );
445 return false;
448 return Util::saveContents( parent, contents );
451 bool Util::saveMessageInMbox( const QList<Akonadi::Item>& retrievedMsgs, QWidget *parent)
454 QString fileName;
455 if ( retrievedMsgs.isEmpty() )
456 return true;
457 const Akonadi::Item msgBase = retrievedMsgs.first();
459 fileName = MessageCore::StringUtil::cleanFileName(MessageViewer::NodeHelper::cleanSubject ( msgBase.payload<KMime::Message::Ptr>().get() ).trimmed() );
461 if ( !fileName.endsWith( QLatin1String( ".mbox" ) ) )
462 fileName += ".mbox";
464 const QString filter = i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" );
465 const KUrl url = KFileDialog::getSaveUrl( KUrl::fromPath( fileName ), filter, parent );
467 if ( url.isEmpty() )
468 return true;
470 const QString localFileName = url.toLocalFile();
471 if ( localFileName.isEmpty() )
472 return true;
474 KMBox::MBox mbox;
475 if ( !mbox.load( localFileName ) ) {
476 //TODO: error
477 return false;
480 foreach ( const Akonadi::Item &item, retrievedMsgs ) {
481 if ( item.hasPayload<KMime::Message::Ptr>() ) {
482 mbox.appendMessage( item.payload<KMime::Message::Ptr>() );
486 if ( !mbox.save() ) {
487 //TODO: error
488 return false;
490 return true;