PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/kdepim-4.8.97/messageviewer/util.cpp

#
C++ | 545 lines | 424 code | 63 blank | 58 comment | 108 complexity | 0f0178634eff6fa7fa96e719f6183a95 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, CC0-1.0, GPL-2.0
  1. /*******************************************************************************
  2. **
  3. ** Filename : util
  4. ** Created on : 03 April, 2005
  5. ** Copyright : (c) 2005 Till Adam
  6. ** Email : <adam@kde.org>
  7. **
  8. *******************************************************************************/
  9. /*******************************************************************************
  10. **
  11. ** This program is free software; you can redistribute it and/or modify
  12. ** it under the terms of the GNU General Public License as published by
  13. ** the Free Software Foundation; either version 2 of the License, or
  14. ** (at your option) any later version.
  15. **
  16. ** It is distributed in the hope that it will be useful, but
  17. ** WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. ** General Public License for more details.
  20. **
  21. ** You should have received a copy of the GNU General Public License
  22. ** along with this program; if not, write to the Free Software
  23. ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. **
  25. ** In addition, as a special exception, the copyright holders give
  26. ** permission to link the code of this program with any edition of
  27. ** the Qt library by Trolltech AS, Norway (or with modified versions
  28. ** of Qt that use the same license as Qt), and distribute linked
  29. ** combinations including the two. You must obey the GNU General
  30. ** Public License in all respects for all of the code used other than
  31. ** Qt. If you modify this file, you may extend this exception to
  32. ** your version of the file, but you are not obligated to do so. If
  33. ** you do not wish to do so, delete this exception statement from
  34. ** your version.
  35. **
  36. *******************************************************************************/
  37. #include <config-messageviewer.h>
  38. #include "util.h"
  39. #include "iconnamecache.h"
  40. #include "nodehelper.h"
  41. #include "renamefiledialog.h"
  42. #include "messagecore/globalsettings.h"
  43. #include "messagecore/nodehelper.h"
  44. #include "messagecore/stringutil.h"
  45. #include <akonadi/item.h>
  46. #include <kmbox/mbox.h>
  47. #include <KMime/Message>
  48. #include <kcharsets.h>
  49. #include <KFileDialog>
  50. #include <kglobal.h>
  51. #include <klocale.h>
  52. #include <kmessagebox.h>
  53. #include <kio/netaccess.h>
  54. #include <kdebug.h>
  55. #include <KMimeType>
  56. #include <KTemporaryFile>
  57. #include <ktoolinvocation.h>
  58. #include <QTextCodec>
  59. #include <QWidget>
  60. #include <QDBusInterface>
  61. #include <QDBusConnectionInterface>
  62. using namespace MessageViewer;
  63. bool Util::checkOverwrite( const KUrl &url, QWidget *w )
  64. {
  65. if ( KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, w ) ) {
  66. if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
  67. w,
  68. i18n( "A file named \"%1\" already exists. "
  69. "Are you sure you want to overwrite it?", url.prettyUrl() ),
  70. i18n( "Overwrite File?" ),
  71. KStandardGuiItem::overwrite() ) )
  72. return false;
  73. }
  74. return true;
  75. }
  76. QString Util::fileNameForMimetype( const QString &mimeType, int iconSize,
  77. const QString &fallbackFileName1,
  78. const QString &fallbackFileName2 )
  79. {
  80. QString fileName;
  81. QString tMimeType = mimeType;
  82. // convert non-registered types to registered types
  83. if ( mimeType == QLatin1String( "application/x-vnd.kolab.contact" ) ) {
  84. tMimeType = QLatin1String( "text/x-vcard" );
  85. } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.event" ) ) {
  86. tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.event" );
  87. } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.task" ) ) {
  88. tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.todo" );
  89. } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.journal" ) ) {
  90. tMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.journal" );
  91. } else if ( mimeType == QLatin1String( "application/x-vnd.kolab.note" ) ) {
  92. tMimeType = QLatin1String( "application/x-vnd.akonadi.note" );
  93. }
  94. KMimeType::Ptr mime = KMimeType::mimeType( tMimeType, KMimeType::ResolveAliases );
  95. if ( mime ) {
  96. fileName = mime->iconName();
  97. } else {
  98. fileName = QLatin1String( "unknown" );
  99. if ( !tMimeType.isEmpty() ) {
  100. kWarning() << "unknown mimetype" << tMimeType;
  101. }
  102. }
  103. if ( fileName.isEmpty() ) {
  104. fileName = fallbackFileName1;
  105. if ( fileName.isEmpty() ) {
  106. fileName = fallbackFileName2;
  107. }
  108. if ( !fileName.isEmpty() ) {
  109. fileName = KMimeType::findByPath( "/tmp/" + fileName, 0, true )->iconName();
  110. }
  111. }
  112. return IconNameCache::instance()->iconPath( fileName, iconSize );
  113. }
  114. #if defined Q_WS_WIN || defined Q_WS_MACX
  115. #include <QDesktopServices>
  116. #endif
  117. bool Util::handleUrlWithQDesktopServices( const KUrl& url )
  118. {
  119. #if defined Q_WS_WIN || defined Q_WS_MACX
  120. QDesktopServices::openUrl( url );
  121. return true;
  122. #else
  123. Q_UNUSED( url );
  124. return false;
  125. #endif
  126. }
  127. QList<KMime::Content*> Util::allContents( const KMime::Content *message )
  128. {
  129. KMime::Content::List result;
  130. KMime::Content *child = MessageCore::NodeHelper::firstChild( message );
  131. if ( child ) {
  132. result += child;
  133. result += allContents( child );
  134. }
  135. KMime::Content *next = MessageCore::NodeHelper::nextSibling( message );
  136. if ( next ) {
  137. result += next;
  138. result += allContents( next );
  139. }
  140. return result;
  141. }
  142. QList<KMime::Content*> Util::extractAttachments( const KMime::Message *message )
  143. {
  144. const KMime::Content::List contents = allContents( message );
  145. KMime::Content::List result;
  146. for ( KMime::Content::List::const_iterator it = contents.constBegin();
  147. it != contents.constEnd(); ) {
  148. KMime::Content* content = *it;
  149. if ( content->contentDisposition()->filename().trimmed().isEmpty() &&
  150. ( content->contentType()->name().trimmed().isEmpty() ||
  151. content == message ) ) {
  152. ++it;
  153. } else {
  154. result <<( *it );
  155. ++it;
  156. }
  157. }
  158. return result;
  159. }
  160. bool Util::saveContents( QWidget *parent, const QList<KMime::Content*> &contents )
  161. {
  162. KUrl url, dirUrl;
  163. if ( contents.count() > 1 ) {
  164. // get the dir
  165. dirUrl = KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
  166. parent,
  167. i18n( "Save Attachments To" ) );
  168. if ( !dirUrl.isValid() ) {
  169. return false;
  170. }
  171. // we may not get a slash-terminated url out of KFileDialog
  172. dirUrl.adjustPath( KUrl::AddTrailingSlash );
  173. }
  174. else {
  175. // only one item, get the desired filename
  176. KMime::Content *content = contents.first();
  177. QString fileName = NodeHelper::fileName( content );
  178. fileName = MessageCore::StringUtil::cleanFileName( fileName );
  179. if ( fileName.isEmpty() ) {
  180. fileName = i18nc( "filename for an unnamed attachment", "attachment.1" );
  181. }
  182. url = KFileDialog::getSaveUrl( KUrl( "kfiledialog:///saveAttachment/" + fileName ),
  183. QString(),
  184. parent,
  185. i18n( "Save Attachment" ) );
  186. if ( url.isEmpty() ) {
  187. return false;
  188. }
  189. }
  190. QMap< QString, int > renameNumbering;
  191. bool globalResult = true;
  192. int unnamedAtmCount = 0;
  193. MessageViewer::RenameFileDialog::RenameFileDialogResult result = MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE;
  194. foreach( KMime::Content *content, contents ) {
  195. KUrl curUrl;
  196. if ( !dirUrl.isEmpty() ) {
  197. curUrl = dirUrl;
  198. QString fileName = MessageViewer::NodeHelper::fileName( content );
  199. fileName = MessageCore::StringUtil::cleanFileName( fileName );
  200. if ( fileName.isEmpty() ) {
  201. ++unnamedAtmCount;
  202. fileName = i18nc( "filename for the %1-th unnamed attachment",
  203. "attachment.%1", unnamedAtmCount );
  204. }
  205. curUrl.setFileName( fileName );
  206. } else {
  207. curUrl = url;
  208. }
  209. if ( !curUrl.isEmpty() ) {
  210. // Rename the file if we have already saved one with the same name:
  211. // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
  212. QString origFile = curUrl.fileName();
  213. QString file = origFile;
  214. while ( renameNumbering.contains(file) ) {
  215. file = origFile;
  216. int num = renameNumbering[file] + 1;
  217. int dotIdx = file.lastIndexOf('.');
  218. file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
  219. }
  220. curUrl.setFileName(file);
  221. // Increment the counter for both the old and the new filename
  222. if ( !renameNumbering.contains(origFile))
  223. renameNumbering[origFile] = 1;
  224. else
  225. renameNumbering[origFile]++;
  226. if ( file != origFile ) {
  227. if ( !renameNumbering.contains(file))
  228. renameNumbering[file] = 1;
  229. else
  230. renameNumbering[file]++;
  231. }
  232. if( !(result == MessageViewer::RenameFileDialog::RENAMEFILE_OVERWRITEALL ||
  233. result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ))
  234. {
  235. if ( KIO::NetAccess::exists( curUrl, KIO::NetAccess::DestinationSide, parent ) ) {
  236. if ( contents.count() == 1 ) {
  237. RenameFileDialog *dlg = new RenameFileDialog(curUrl,false, parent);
  238. result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
  239. if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE )
  240. {
  241. delete dlg;
  242. continue;
  243. }
  244. else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
  245. {
  246. curUrl = dlg->newName();
  247. }
  248. delete dlg;
  249. }
  250. else {
  251. RenameFileDialog *dlg = new RenameFileDialog(curUrl,true, parent);
  252. result = static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
  253. if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE ||
  254. result == MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL )
  255. {
  256. delete dlg;
  257. continue;
  258. }
  259. else if ( result == MessageViewer::RenameFileDialog::RENAMEFILE_RENAME )
  260. {
  261. curUrl = dlg->newName();
  262. }
  263. delete dlg;
  264. }
  265. }
  266. }
  267. // save
  268. if( result != MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL ) {
  269. const bool result = saveContent( parent, content, curUrl );
  270. if ( !result )
  271. globalResult = result;
  272. }
  273. }
  274. }
  275. return globalResult;
  276. }
  277. bool Util::saveContent( QWidget *parent, KMime::Content* content, const KUrl& url )
  278. {
  279. // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
  280. // reading out the encryption/signature state will not work at all.
  281. // Then, topLevel() will not work for attachments that are inside encrypted parts.
  282. // What should actually be done is either passing in an ObjectTreeParser that has already
  283. // parsed the message, or creating an OTP here (which would have the downside that the
  284. // password dialog for decrypting messages is shown twice)
  285. #if 0 // totally broken
  286. KMime::Content *topContent = content->topLevel();
  287. MessageViewer::NodeHelper *mNodeHelper = new MessageViewer::NodeHelper;
  288. bool bSaveEncrypted = false;
  289. bool bEncryptedParts = mNodeHelper->encryptionState( content ) != MessageViewer::KMMsgNotEncrypted;
  290. if( bEncryptedParts )
  291. if( KMessageBox::questionYesNo( parent,
  292. i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
  293. url.fileName() ),
  294. i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
  295. KMessageBox::Yes )
  296. bSaveEncrypted = true;
  297. bool bSaveWithSig = true;
  298. if(mNodeHelper->signatureState( content ) != MessageViewer::KMMsgNotSigned )
  299. if( KMessageBox::questionYesNo( parent,
  300. i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?",
  301. url.fileName() ),
  302. i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
  303. KMessageBox::Yes )
  304. bSaveWithSig = false;
  305. QByteArray data;
  306. if( bSaveEncrypted || !bEncryptedParts) {
  307. KMime::Content *dataNode = content;
  308. QByteArray rawDecryptedBody;
  309. bool gotRawDecryptedBody = false;
  310. if ( !bSaveWithSig ) {
  311. if ( topContent->contentType()->mimeType() == "multipart/signed" ) {
  312. // carefully look for the part that is *not* the signature part:
  313. if ( ObjectTreeParser::findType( topContent, "application/pgp-signature", true, false ) ) {
  314. dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pgp-signature", true, false );
  315. } else if ( ObjectTreeParser::findType( topContent, "application/pkcs7-mime" , true, false ) ) {
  316. dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pkcs7-mime", true, false );
  317. } else {
  318. dataNode = ObjectTreeParser::findTypeNot( topContent, "multipart", "", true, false );
  319. }
  320. } else {
  321. EmptySource emptySource;
  322. ObjectTreeParser otp( &emptySource, 0, 0, false, false );
  323. // process this node and all it's siblings and descendants
  324. mNodeHelper->setNodeUnprocessed( dataNode, true );
  325. otp.parseObjectTree( dataNode );
  326. rawDecryptedBody = otp.rawDecryptedBody();
  327. gotRawDecryptedBody = true;
  328. }
  329. }
  330. QByteArray cstr = gotRawDecryptedBody
  331. ? rawDecryptedBody
  332. : dataNode->decodedContent();
  333. data = KMime::CRLFtoLF( cstr );
  334. }
  335. #else
  336. const QByteArray data = content->decodedContent();
  337. kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
  338. #endif
  339. QDataStream ds;
  340. QFile file;
  341. KTemporaryFile tf;
  342. if ( url.isLocalFile() )
  343. {
  344. // save directly
  345. file.setFileName( url.toLocalFile() );
  346. if ( !file.open( QIODevice::WriteOnly ) )
  347. {
  348. KMessageBox::error( parent,
  349. i18nc( "1 = file name, 2 = error string",
  350. "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
  351. file.fileName(),
  352. file.errorString() ),
  353. i18n( "Error saving attachment" ) );
  354. return false;
  355. }
  356. const int permissions = MessageViewer::Util::getWritePermissions();
  357. if ( permissions >= 0 )
  358. fchmod( file.handle(), permissions );
  359. ds.setDevice( &file );
  360. } else
  361. {
  362. // tmp file for upload
  363. tf.open();
  364. ds.setDevice( &tf );
  365. }
  366. const int bytesWritten = ds.writeRawData( data.data(), data.size() );
  367. if ( bytesWritten != data.size() ) {
  368. QFile *f = static_cast<QFile *>( ds.device() );
  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. f->fileName(),
  373. f->errorString() ),
  374. i18n( "Error saving attachment" ) );
  375. // Remove the newly created empty or partial file
  376. f->remove();
  377. return false;
  378. }
  379. if ( !url.isLocalFile() )
  380. {
  381. // QTemporaryFile::fileName() is only defined while the file is open
  382. QString tfName = tf.fileName();
  383. tf.close();
  384. if ( !KIO::NetAccess::upload( tfName, url, parent ) )
  385. {
  386. KMessageBox::error( parent,
  387. i18nc( "1 = file name, 2 = error string",
  388. "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
  389. url.prettyUrl(),
  390. KIO::NetAccess::lastErrorString() ),
  391. i18n( "Error saving attachment" ) );
  392. return false;
  393. }
  394. }
  395. else
  396. file.close();
  397. #if 0
  398. mNodeHelper->removeTempFiles();
  399. delete mNodeHelper;
  400. #endif
  401. return true;
  402. }
  403. int Util::getWritePermissions()
  404. {
  405. // #79685, #232001 by default use the umask the user defined, but let it be configurable
  406. if ( MessageCore::GlobalSettings::self()->disregardUmask() ) {
  407. return S_IRUSR | S_IWUSR;
  408. } else {
  409. return -1;
  410. }
  411. }
  412. bool Util::saveAttachments( const KMime::Content::List& contents, QWidget *parent )
  413. {
  414. if ( contents.isEmpty() ) {
  415. KMessageBox::information( parent, i18n( "Found no attachments to save." ) );
  416. return false;
  417. }
  418. return Util::saveContents( parent, contents );
  419. }
  420. bool Util::saveMessageInMbox( const QList<Akonadi::Item>& retrievedMsgs, QWidget *parent, bool appendMessages )
  421. {
  422. QString fileName;
  423. if ( retrievedMsgs.isEmpty() )
  424. return true;
  425. const Akonadi::Item msgBase = retrievedMsgs.first();
  426. if( msgBase.hasPayload<KMime::Message::Ptr>() )
  427. fileName = MessageCore::StringUtil::cleanFileName(MessageViewer::NodeHelper::cleanSubject ( msgBase.payload<KMime::Message::Ptr>().get() ).trimmed() );
  428. else
  429. fileName = i18n("message");
  430. if ( !fileName.endsWith( QLatin1String( ".mbox" ) ) )
  431. fileName += ".mbox";
  432. const QString filter = i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" );
  433. KFileDialog::Option options = static_cast<KFileDialog::Option>(0);
  434. if( !appendMessages )
  435. options = KFileDialog::ConfirmOverwrite;
  436. const KUrl url = KFileDialog::getSaveUrl( KUrl::fromPath( fileName ), filter, parent, i18np("Save Message", "Save Messages", retrievedMsgs.count() ), options);
  437. if ( url.isEmpty() )
  438. return true;
  439. const QString localFileName = url.toLocalFile();
  440. if ( localFileName.isEmpty() )
  441. return true;
  442. if( !appendMessages ) {
  443. QFile::remove(localFileName);
  444. }
  445. KMBox::MBox mbox;
  446. if ( !mbox.load( localFileName ) ) {
  447. if( appendMessages ) {
  448. KMessageBox::error( parent, i18n("File %1 could not be loaded.",localFileName) , i18n( "Error loading message" ) );
  449. } else {
  450. KMessageBox::error( parent, i18n("File %1 could not be created.",localFileName) , i18n( "Error saving message" ) );
  451. }
  452. return false;
  453. }
  454. foreach ( const Akonadi::Item &item, retrievedMsgs ) {
  455. if ( item.hasPayload<KMime::Message::Ptr>() ) {
  456. mbox.appendMessage( item.payload<KMime::Message::Ptr>() );
  457. }
  458. }
  459. if ( !mbox.save() ) {
  460. KMessageBox::error( parent, i18n("We can not save message.") , i18n( "Error saving message" ) );
  461. return false;
  462. }
  463. return true;
  464. }
  465. bool Util::speakSelectedText( const QString& text, QWidget *parent)
  466. {
  467. if(text.isEmpty())
  468. return false;
  469. // If KTTSD not running, start it.
  470. if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd"))
  471. {
  472. QString error;
  473. if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error))
  474. {
  475. KMessageBox::error(parent, i18n( "Starting Jovie Text-to-Speech Service Failed"), error );
  476. return false;
  477. }
  478. }
  479. QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech");
  480. ktts.asyncCall("say", text, 0);
  481. return true;
  482. }