kilobyte is kB not kb
[kdepim.git] / messageviewer / util.cpp
blob7c42ac8fa6b85eebcf20fd057f8ab75e3a55c8b5
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>
66 #include <ktoolinvocation.h>
68 #include <QTextCodec>
69 #include <QWidget>
70 #include <QDBusInterface>
71 #include <QDBusConnectionInterface>
73 using namespace MessageViewer;
75 bool Util::checkOverwrite( const KUrl &url, QWidget *w )
77 if ( KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, w ) ) {
78 if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
80 i18n( "A file named \"%1\" already exists. "
81 "Are you sure you want to overwrite it?", url.prettyUrl() ),
82 i18n( "Overwrite File?" ),
83 KStandardGuiItem::overwrite() ) )
84 return false;
86 return true;
89 QString Util::fileNameForMimetype( const QString &mimeType, int iconSize,
90 const QString &fallbackFileName1,
91 const QString &fallbackFileName2 )
93 QString fileName;
94 QString tMimeType = mimeType;
96 // convert non-registered types to registered types
97 if ( mimeType == QLatin1String( "application/x-vnd.kolab.contact" ) ) {
98 tMimeType = QLatin1String( "text/x-vcard" );
99 } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.event" ) ) {
100 tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.event" );
101 } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.task" ) ) {
102 tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.todo" );
103 } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.journal" ) ) {
104 tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.journal" );
105 } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.note" ) ) {
106 tMimeType = QLatin1String( "application/x-vnd.akonadi.note" );
109 KMimeType::Ptr mime = KMimeType::mimeType( tMimeType, KMimeType::ResolveAliases );
110 if ( mime ) {
111 fileName = mime->iconName();
112 } else {
113 fileName = QLatin1String( "unknown" );
114 if ( !tMimeType.isEmpty() ) {
115 kWarning() << "unknown mimetype" << tMimeType;
119 if ( fileName.isEmpty() ) {
120 fileName = fallbackFileName1;
121 if ( fileName.isEmpty() ) {
122 fileName = fallbackFileName2;
124 if ( !fileName.isEmpty() ) {
125 fileName = KMimeType::findByPath( "/tmp/" + fileName, 0, true )->iconName();
129 return IconNameCache::instance()->iconPath( fileName, iconSize );
132 #if defined Q_WS_WIN || defined Q_WS_MACX
133 #include <QDesktopServices>
134 #endif
136 bool Util::handleUrlWithQDesktopServices( const KUrl& url )
138 #if defined Q_WS_WIN || defined Q_WS_MACX
139 QDesktopServices::openUrl( url );
140 return true;
141 #else
142 Q_UNUSED( url );
143 return false;
144 #endif
147 QList<KMime::Content*> Util::allContents( const KMime::Content *message )
149 KMime::Content::List result;
150 KMime::Content *child = MessageCore::NodeHelper::firstChild( message );
151 if ( child ) {
152 result += child;
153 result += allContents( child );
155 KMime::Content *next = MessageCore::NodeHelper::nextSibling( message );
156 if ( next ) {
157 result += next;
158 result += allContents( next );
161 return result;
164 QList<KMime::Content*> Util::extractAttachments( const KMime::Message *message )
166 const KMime::Content::List contents = allContents( message );
167 KMime::Content::List result;
168 for ( KMime::Content::List::const_iterator it = contents.constBegin();
169 it != contents.constEnd(); ) {
170 KMime::Content* content = *it;
171 if ( content->contentDisposition()->filename().trimmed().isEmpty() &&
172 ( content->contentType()->name().trimmed().isEmpty() ||
173 content == message ) ) {
174 ++it;
175 } else {
176 result <<( *it );
177 ++it;
181 return result;
184 bool Util::saveContents( QWidget *parent, const QList<KMime::Content*> &contents )
186 KUrl url, dirUrl;
187 if ( contents.count() > 1 ) {
188 // get the dir
189 dirUrl = KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
190 parent,
191 i18n( "Save Attachments To" ) );
192 if ( !dirUrl.isValid() ) {
193 return false;
196 // we may not get a slash-terminated url out of KFileDialog
197 dirUrl.adjustPath( KUrl::AddTrailingSlash );
199 else {
200 // only one item, get the desired filename
201 KMime::Content *content = contents.first();
202 QString fileName = NodeHelper::fileName( content );
203 fileName = MessageCore::StringUtil::cleanFileName( fileName );
204 if ( fileName.isEmpty() ) {
205 fileName = i18nc( "filename for an unnamed attachment", "attachment.1" );
207 url = KFileDialog::getSaveUrl( KUrl( "kfiledialog:///saveAttachment/" + fileName ),
208 QString(),
209 parent,
210 i18n( "Save Attachment" ) );
211 if ( url.isEmpty() ) {
212 return false;
216 QMap< QString, int > renameNumbering;
218 bool globalResult = true;
219 int unnamedAtmCount = 0;
220 MessageViewer::RenameFileDialog::RenameFileDialogResult result = MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE;
221 foreach( KMime::Content *content, contents ) {
222 KUrl curUrl;
223 if ( !dirUrl.isEmpty() ) {
224 curUrl = dirUrl;
225 QString fileName = MessageViewer::NodeHelper::fileName( content );
226 fileName = MessageCore::StringUtil::cleanFileName( fileName );
227 if ( fileName.isEmpty() ) {
228 ++unnamedAtmCount;
229 fileName = i18nc( "filename for the %1-th unnamed attachment",
230 "attachment.%1", unnamedAtmCount );
232 curUrl.setFileName( fileName );
233 } else {
234 curUrl = url;
237 if ( !curUrl.isEmpty() ) {
239 // Rename the file if we have already saved one with the same name:
240 // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
241 QString origFile = curUrl.fileName();
242 QString file = origFile;
244 while ( renameNumbering.contains(file) ) {
245 file = origFile;
246 int num = renameNumbering[file] + 1;
247 int dotIdx = file.lastIndexOf('.');
248 file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
250 curUrl.setFileName(file);
252 // Increment the counter for both the old and the new filename
253 if ( !renameNumbering.contains(origFile))
254 renameNumbering[origFile] = 1;
255 else
256 renameNumbering[origFile]++;
258 if ( file != origFile ) {
259 if ( !renameNumbering.contains(file))
260 renameNumbering[file] = 1;
261 else
262 renameNumbering[file]++;
266 if( !(result == MessageViewer::RenameFileDialog::RENAMEFILE_OVERWRITEALL ||
267 result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ))
269 if ( KIO::NetAccess::exists( curUrl, KIO::NetAccess::DestinationSide, parent ) ) {
270 if ( contents.count() == 1 ) {
271 RenameFileDialog *dlg = new RenameFileDialog(curUrl,false, parent);
272 result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
273 if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE )
275 delete dlg;
276 continue;
278 else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
280 curUrl = dlg->newName();
282 delete dlg;
284 else {
285 RenameFileDialog *dlg = new RenameFileDialog(curUrl,true, parent);
286 result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
288 if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE ||
289 result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL )
291 delete dlg;
292 continue;
294 else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
296 curUrl = dlg->newName();
298 delete dlg;
302 // save
303 if( result != MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ) {
304 const bool result = saveContent( parent, content, curUrl );
305 if ( !result )
306 globalResult = result;
311 return globalResult;
314 bool Util::saveContent( QWidget *parent, KMime::Content* content, const KUrl& url )
316 // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
317 // reading out the encryption/signature state will not work at all.
318 // Then, topLevel() will not work for attachments that are inside encrypted parts.
319 // What should actually be done is either passing in an ObjectTreeParser that has already
320 // parsed the message, or creating an OTP here (which would have the downside that the
321 // password dialog for decrypting messages is shown twice)
322 #if 0 // totally broken
323 KMime::Content *topContent = content->topLevel();
324 MessageViewer::NodeHelper *mNodeHelper = new MessageViewer::NodeHelper;
325 bool bSaveEncrypted = false;
326 bool bEncryptedParts = mNodeHelper->encryptionState( content ) != MessageViewer::KMMsgNotEncrypted;
327 if( bEncryptedParts )
328 if( KMessageBox::questionYesNo( parent,
329 i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
330 url.fileName() ),
331 i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
332 KMessageBox::Yes )
333 bSaveEncrypted = true;
335 bool bSaveWithSig = true;
336 if(mNodeHelper->signatureState( content ) != MessageViewer::KMMsgNotSigned )
337 if( KMessageBox::questionYesNo( parent,
338 i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?",
339 url.fileName() ),
340 i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
341 KMessageBox::Yes )
342 bSaveWithSig = false;
344 QByteArray data;
345 if( bSaveEncrypted || !bEncryptedParts) {
346 KMime::Content *dataNode = content;
347 QByteArray rawDecryptedBody;
348 bool gotRawDecryptedBody = false;
349 if ( !bSaveWithSig ) {
350 if ( topContent->contentType()->mimeType() == "multipart/signed" ) {
351 // carefully look for the part that is *not* the signature part:
352 if ( ObjectTreeParser::findType( topContent, "application/pgp-signature", true, false ) ) {
353 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pgp-signature", true, false );
354 } else if ( ObjectTreeParser::findType( topContent, "application/pkcs7-mime" , true, false ) ) {
355 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pkcs7-mime", true, false );
356 } else {
357 dataNode = ObjectTreeParser::findTypeNot( topContent, "multipart", "", true, false );
359 } else {
360 EmptySource emptySource;
361 ObjectTreeParser otp( &emptySource, 0, 0, false, false );
363 // process this node and all it's siblings and descendants
364 mNodeHelper->setNodeUnprocessed( dataNode, true );
365 otp.parseObjectTree( dataNode );
367 rawDecryptedBody = otp.rawDecryptedBody();
368 gotRawDecryptedBody = true;
371 QByteArray cstr = gotRawDecryptedBody
372 ? rawDecryptedBody
373 : dataNode->decodedContent();
374 data = KMime::CRLFtoLF( cstr );
376 #else
377 const QByteArray data = content->decodedContent();
378 kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
379 #endif
380 QDataStream ds;
381 QFile file;
382 KTemporaryFile tf;
383 if ( url.isLocalFile() )
385 // save directly
386 file.setFileName( url.toLocalFile() );
387 if ( !file.open( QIODevice::WriteOnly ) )
389 KMessageBox::error( parent,
390 i18nc( "1 = file name, 2 = error string",
391 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
392 file.fileName(),
393 file.errorString() ),
394 i18n( "Error saving attachment" ) );
395 return false;
398 const int permissions = MessageViewer::Util::getWritePermissions();
399 if ( permissions >= 0 )
400 fchmod( file.handle(), permissions );
402 ds.setDevice( &file );
403 } else
405 // tmp file for upload
406 tf.open();
407 ds.setDevice( &tf );
410 const int bytesWritten = ds.writeRawData( data.data(), data.size() );
411 if ( bytesWritten != data.size() ) {
412 QFile *f = static_cast<QFile *>( ds.device() );
413 KMessageBox::error( parent,
414 i18nc( "1 = file name, 2 = error string",
415 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
416 f->fileName(),
417 f->errorString() ),
418 i18n( "Error saving attachment" ) );
419 // Remove the newly created empty or partial file
420 f->remove();
421 return false;
424 if ( !url.isLocalFile() )
426 // QTemporaryFile::fileName() is only defined while the file is open
427 QString tfName = tf.fileName();
428 tf.close();
429 if ( !KIO::NetAccess::upload( tfName, url, parent ) )
431 KMessageBox::error( parent,
432 i18nc( "1 = file name, 2 = error string",
433 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
434 url.prettyUrl(),
435 KIO::NetAccess::lastErrorString() ),
436 i18n( "Error saving attachment" ) );
437 return false;
440 else
441 file.close();
443 #if 0
444 mNodeHelper->removeTempFiles();
445 delete mNodeHelper;
446 #endif
447 return true;
451 int Util::getWritePermissions()
453 // #79685, #232001 by default use the umask the user defined, but let it be configurable
454 if ( MessageCore::GlobalSettings::self()->disregardUmask() ) {
455 return S_IRUSR | S_IWUSR;
456 } else {
457 return -1;
461 bool Util::saveAttachments( const KMime::Content::List& contents, QWidget *parent )
463 if ( contents.isEmpty() ) {
464 KMessageBox::information( parent, i18n( "Found no attachments to save." ) );
465 return false;
468 return Util::saveContents( parent, contents );
471 bool Util::saveMessageInMbox( const QList<Akonadi::Item>& retrievedMsgs, QWidget *parent, bool appendMessages )
474 QString fileName;
475 if ( retrievedMsgs.isEmpty() )
476 return true;
477 const Akonadi::Item msgBase = retrievedMsgs.first();
479 if( msgBase.hasPayload<KMime::Message::Ptr>() )
480 fileName = MessageCore::StringUtil::cleanFileName(MessageViewer::NodeHelper::cleanSubject ( msgBase.payload<KMime::Message::Ptr>().get() ).trimmed() );
481 else
482 fileName = i18n("message");
484 if ( !fileName.endsWith( QLatin1String( ".mbox" ) ) )
485 fileName += ".mbox";
487 const QString filter = i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" );
488 KFileDialog::Option options = static_cast<KFileDialog::Option>(0);
489 if( !appendMessages )
490 options = KFileDialog::ConfirmOverwrite;
491 const KUrl url = KFileDialog::getSaveUrl( KUrl::fromPath( fileName ), filter, parent, i18np("Save Message", "Save Messages", retrievedMsgs.count() ), options);
493 if ( url.isEmpty() )
494 return true;
496 const QString localFileName = url.toLocalFile();
497 if ( localFileName.isEmpty() )
498 return true;
500 if( !appendMessages ) {
501 QFile::remove(localFileName);
504 KMBox::MBox mbox;
505 if ( !mbox.load( localFileName ) ) {
506 if( appendMessages ) {
507 KMessageBox::error( parent, i18n("File %1 could not be loaded.",localFileName) , i18n( "Error loading message" ) );
508 } else {
509 KMessageBox::error( parent, i18n("File %1 could not be created.",localFileName) , i18n( "Error saving message" ) );
511 return false;
513 foreach ( const Akonadi::Item &item, retrievedMsgs ) {
514 if ( item.hasPayload<KMime::Message::Ptr>() ) {
515 mbox.appendMessage( item.payload<KMime::Message::Ptr>() );
519 if ( !mbox.save() ) {
520 KMessageBox::error( parent, i18n("We can not save message.") , i18n( "Error saving message" ) );
521 return false;
523 return true;
527 bool Util::speakSelectedText( const QString& text, QWidget *parent)
529 if(text.isEmpty())
530 return false;
532 // If KTTSD not running, start it.
533 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd"))
535 QString error;
536 if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error))
538 KMessageBox::error(parent, i18n( "Starting Jovie Text-to-Speech Service Failed"), error );
539 return false;
542 QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech");
543 ktts.asyncCall("say", text, 0);
544 return true;