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 <qsocketnotifier.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 <fcntl.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 #include <linux/inotify.h>
00048 
00049 static inline int inotify_init (void)
00050 {
00051   return syscall (__NR_inotify_init);
00052 }
00053 
00054 static inline int inotify_add_watch (int fd, const char *name, __u32 mask)
00055 {
00056   return syscall (__NR_inotify_add_watch, fd, name, mask);
00057 }
00058 
00059 static inline int inotify_rm_watch (int fd, __u32 wd)
00060 {
00061   return syscall (__NR_inotify_rm_watch, fd, wd);
00062 }
00063 #endif
00064 
00065 using namespace KMail;
00066 
00067 EditorWatcher::EditorWatcher(const KURL & url, const QString &mimeType, bool openWith,
00068                              QObject * parent, QWidget *parentWidget) :
00069     QObject( parent ),
00070     mUrl( url ),
00071     mMimeType( mimeType ),
00072     mOpenWith( openWith ),
00073     mEditor( 0 ),
00074     mParentWidget( parentWidget ),
00075     mHaveInotify( false ),
00076     mFileOpen( false ),
00077     mEditorRunning( false ),
00078     mFileModified( true ), // assume the worst unless we know better
00079     mDone( false )
00080 {
00081   assert( mUrl.isLocalFile() );
00082   connect( &mTimer, SIGNAL(timeout()), SLOT(checkEditDone()) );
00083 }
00084 
00085 bool EditorWatcher::start()
00086 {
00087   // find an editor
00088   KURL::List list;
00089   list.append( mUrl );
00090   KService::Ptr offer = KServiceTypeProfile::preferredService( mMimeType, "Application" );
00091   if ( mOpenWith || !offer ) {
00092     KOpenWithDlg dlg( list, i18n("Edit with:"), QString::null, 0 );
00093     if ( !dlg.exec() )
00094       return false;
00095     offer = dlg.service();
00096     if ( !offer )
00097       return false;
00098   }
00099 
00100 #ifdef HAVE_INOTIFY
00101   // monitor file
00102   mInotifyFd = inotify_init();
00103   if ( mInotifyFd > 0 ) {
00104     mInotifyWatch = inotify_add_watch( mInotifyFd, mUrl.path().latin1(), IN_CLOSE | IN_OPEN | IN_MODIFY );
00105     if ( mInotifyWatch >= 0 ) {
00106       QSocketNotifier *sn = new QSocketNotifier( mInotifyFd, QSocketNotifier::Read, this );
00107       connect( sn, SIGNAL(activated(int)), SLOT(inotifyEvent()) );
00108       mHaveInotify = true;
00109       mFileModified = false;
00110     }
00111   } else {
00112     kdWarning(5006) << k_funcinfo << "Failed to activate INOTIFY!" << endl;
00113   }
00114 #endif
00115 
00116   // start the editor
00117   QStringList params = KRun::processDesktopExec( *offer, list, false );
00118   mEditor = new KProcess( this );
00119   *mEditor << params;
00120   connect( mEditor, SIGNAL(processExited(KProcess*)), SLOT(editorExited()) );
00121   if ( !mEditor->start() )
00122     return false;
00123   mEditorRunning = true;
00124 
00125   mEditTime.start();
00126   return true;
00127 }
00128 
00129 void EditorWatcher::inotifyEvent()
00130 {
00131   assert( mHaveInotify );
00132 #ifdef HAVE_INOTIFY
00133   int pending = -1;
00134   char buffer[4096];
00135   ioctl( mInotifyFd, FIONREAD, &pending );
00136   while ( pending > 0 ) {
00137     int size = read( mInotifyFd, buffer, QMIN( pending, (int)sizeof(buffer) ) );
00138     pending -= size;
00139     if ( size < 0 )
00140       break; // error
00141     int offset = 0;
00142     while ( size > 0 ) {
00143       struct inotify_event *event = (struct inotify_event *) &buffer[offset];
00144       size -= sizeof( struct inotify_event ) + event->len;
00145       offset += sizeof( struct inotify_event ) + event->len;
00146       if ( event->mask & IN_OPEN )
00147         mFileOpen = true;
00148       if ( event->mask & IN_CLOSE )
00149         mFileOpen = false;
00150       if ( event->mask & IN_MODIFY )
00151         mFileModified = true;
00152     }
00153   }
00154 #endif
00155   mTimer.start( 500, true );
00156 
00157 }
00158 
00159 void EditorWatcher::editorExited()
00160 {
00161   mEditorRunning = false;
00162   mTimer.start( 500, true );
00163 }
00164 
00165 void EditorWatcher::checkEditDone()
00166 {
00167   if ( mEditorRunning || (mFileOpen && mHaveInotify) || mDone )
00168     return;
00169   // protect us against double-deletion by calling this method again while
00170   // the subeventloop of the message box is running
00171   mDone = true;
00172   // nobody can edit that fast, we seem to be unable to detect
00173   // when the editor will be closed
00174   if ( mEditTime.elapsed() <= 3000 ) {
00175     KMessageBox::error( mParentWidget,
00176                         i18n( "KMail is unable to detect when the choosen editor is closed. "
00177                               "To avoid data loss, editing the attachment will be aborted." ),
00178                         i18n( "Unable to edit attachment" ) );
00179   }
00180 
00181   emit editDone( this );
00182   deleteLater();
00183 }
00184 
00185 #include "editorwatcher.moc"