kmail

editorwatcher.cpp

00001 /*
00002     Copyright (c) 2007 Volker Krause <vkrause@kde.org>
00003 
00004     This program is free software; you can redistribute it and/or modify
00005     it under the terms of the GNU General Public License as published by
00006     the Free Software Foundation; either version 2 of the License, or
00007     (at your option) any later version.
00008 
00009     This program is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00012     GNU General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017 */
00018 
00019 #include "editorwatcher.h"
00020 
00021 #include <config.h>
00022 
00023 #include <kdebug.h>
00024 #include <klocale.h>
00025 #include <kmessagebox.h>
00026 #include <kopenwith.h>
00027 #include <kprocess.h>
00028 #include <kuserprofile.h>
00029 
00030 #include <tqsocketnotifier.h>
00031 
00032 #include <cassert>
00033 
00034 // inotify stuff taken from kdelibs/kio/kio/kdirwatch.cpp
00035 #ifdef HAVE_SYS_INOTIFY
00036 #include <sys/ioctl.h>
00037 #include <sys/inotify.h>
00038 #include <fcntl.h>
00039 #elif HAVE_INOTIFY
00040 #include <sys/ioctl.h>
00041 #include <unistd.h>
00042 #include <sys/inotify.h>
00043 #include <sys/syscall.h>
00044 #include <linux/types.h>
00045 // Linux kernel headers are documented to not compile
00046 #define _S390_BITOPS_H
00047 #endif
00048 
00049 using namespace KMail;
00050 
00051 EditorWatcher::EditorWatcher(const KURL & url, const TQString &mimeType, bool openWith,
00052                              TQObject * parent, TQWidget *parentWidget) :
00053     TQObject( parent ),
00054     mUrl( url ),
00055     mMimeType( mimeType ),
00056     mOpenWith( openWith ),
00057     mEditor( 0 ),
00058     mParentWidget( parentWidget ),
00059     mHaveInotify( false ),
00060     mFileOpen( false ),
00061     mEditorRunning( false ),
00062     mFileModified( true ), // assume the worst unless we know better
00063     mDone( false )
00064 {
00065   assert( mUrl.isLocalFile() );
00066   connect( &mTimer, TQT_SIGNAL(timeout()), TQT_SLOT(checkEditDone()) );
00067 }
00068 
00069 bool EditorWatcher::start()
00070 {
00071   // find an editor
00072   KURL::List list;
00073   list.append( mUrl );
00074   KService::Ptr offer = KServiceTypeProfile::preferredService( mMimeType, "Application" );
00075   if ( mOpenWith || !offer ) {
00076     KOpenWithDlg dlg( list, i18n("Edit with:"), TQString(), 0 );
00077     if ( !dlg.exec() )
00078       return false;
00079     offer = dlg.service();
00080     if ( !offer )
00081       return false;
00082   }
00083 
00084 #ifdef HAVE_INOTIFY
00085   // monitor file
00086   mInotifyFd = inotify_init();
00087   if ( mInotifyFd > 0 ) {
00088     mInotifyWatch = inotify_add_watch( mInotifyFd, mUrl.path().latin1(), IN_CLOSE | IN_OPEN | IN_MODIFY );
00089     if ( mInotifyWatch >= 0 ) {
00090       TQSocketNotifier *sn = new TQSocketNotifier( mInotifyFd, TQSocketNotifier::Read, this );
00091       connect( sn, TQT_SIGNAL(activated(int)), TQT_SLOT(inotifyEvent()) );
00092       mHaveInotify = true;
00093       mFileModified = false;
00094     }
00095   } else {
00096     kdWarning(5006) << k_funcinfo << "Failed to activate INOTIFY!" << endl;
00097   }
00098 #endif
00099 
00100   // start the editor
00101   TQStringList params = KRun::processDesktopExec( *offer, list, false );
00102   mEditor = new KProcess( this );
00103   *mEditor << params;
00104   connect( mEditor, TQT_SIGNAL(processExited(KProcess*)), TQT_SLOT(editorExited()) );
00105   if ( !mEditor->start() )
00106     return false;
00107   mEditorRunning = true;
00108 
00109   mEditTime.start();
00110   return true;
00111 }
00112 
00113 void EditorWatcher::inotifyEvent()
00114 {
00115   assert( mHaveInotify );
00116 #ifdef HAVE_INOTIFY
00117   int pending = -1;
00118   char buffer[4096];
00119   ioctl( mInotifyFd, FIONREAD, &pending );
00120   while ( pending > 0 ) {
00121     int size = read( mInotifyFd, buffer, TQMIN( pending, (int)sizeof(buffer) ) );
00122     pending -= size;
00123     if ( size < 0 )
00124       break; // error
00125     int offset = 0;
00126     while ( size > 0 ) {
00127       struct inotify_event *event = (struct inotify_event *) &buffer[offset];
00128       size -= sizeof( struct inotify_event ) + event->len;
00129       offset += sizeof( struct inotify_event ) + event->len;
00130       if ( event->mask & IN_OPEN )
00131         mFileOpen = true;
00132       if ( event->mask & IN_CLOSE )
00133         mFileOpen = false;
00134       if ( event->mask & IN_MODIFY )
00135         mFileModified = true;
00136     }
00137   }
00138 #endif
00139   mTimer.start( 500, true );
00140 
00141 }
00142 
00143 void EditorWatcher::editorExited()
00144 {
00145   mEditorRunning = false;
00146   mTimer.start( 500, true );
00147 }
00148 
00149 void EditorWatcher::checkEditDone()
00150 {
00151   if ( mEditorRunning || (mFileOpen && mHaveInotify) || mDone )
00152     return;
00153   // protect us against double-deletion by calling this method again while
00154   // the subeventloop of the message box is running
00155   mDone = true;
00156   // nobody can edit that fast, we seem to be unable to detect
00157   // when the editor will be closed
00158   if ( mEditTime.elapsed() <= 3000 ) {
00159     KMessageBox::information(
00160       mParentWidget,
00161       i18n( "KMail is unable to detect when the chosen editor is closed. "
00162             "To avoid data loss, editing the attachment will be aborted." ),
00163       i18n( "Unable to edit attachment" ),
00164       "UnableToEditAttachment" );
00165 
00166   }
00167 
00168   emit editDone( this );
00169   deleteLater();
00170 }
00171 
00172 #include "editorwatcher.moc"