File indexing completed on 2024-06-23 05:18:24
0001 /* 0002 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "editorwatcher.h" 0008 #include <config-messagecomposer.h> 0009 0010 #include "messagecomposer_debug.h" 0011 0012 #include <KApplicationTrader> 0013 #include <KIO/DesktopExecParser> 0014 #include <KLocalizedString> 0015 #include <KMessageBox> 0016 #include <KOpenWithDialog> 0017 #include <KProcess> 0018 0019 #include <QSocketNotifier> 0020 0021 #include <cassert> 0022 #include <memory> 0023 0024 // inotify stuff taken from kdelibs/kio/kio/kdirwatch.cpp 0025 #ifdef HAVE_SYS_INOTIFY_H 0026 #include <fcntl.h> 0027 #include <sys/inotify.h> 0028 #include <sys/ioctl.h> 0029 #include <unistd.h> 0030 #endif 0031 0032 using namespace MessageComposer; 0033 0034 EditorWatcher::EditorWatcher(const QUrl &url, const QString &mimeType, OpenWithOption option, QObject *parent, QWidget *parentWidget) 0035 : QObject(parent) 0036 , mUrl(url) 0037 , mMimeType(mimeType) 0038 , mParentWidget(parentWidget) 0039 , mOpenWithOption(option) 0040 { 0041 assert(mUrl.isLocalFile()); 0042 mTimer.setSingleShot(true); 0043 connect(&mTimer, &QTimer::timeout, this, &EditorWatcher::checkEditDone); 0044 } 0045 0046 EditorWatcher::~EditorWatcher() 0047 { 0048 #ifdef HAVE_SYS_INOTIFY_H 0049 ::close(mInotifyFd); 0050 #endif 0051 } 0052 0053 EditorWatcher::ErrorEditorWatcher EditorWatcher::start() 0054 { 0055 // find an editor 0056 QList<QUrl> list; 0057 list.append(mUrl); 0058 KService::Ptr offer = KApplicationTrader::preferredService(mMimeType); 0059 if ((mOpenWithOption == OpenWithDialog) || !offer) { 0060 std::unique_ptr<KOpenWithDialog> dlg(new KOpenWithDialog(list, i18n("Edit with:"), QString(), mParentWidget)); 0061 const int dlgrc = dlg->exec(); 0062 if (dlgrc) { 0063 offer = dlg->service(); 0064 } 0065 if (!dlgrc) { 0066 return Canceled; 0067 } 0068 if (!offer) { 0069 return NoServiceFound; 0070 } 0071 } 0072 0073 #ifdef HAVE_SYS_INOTIFY_H 0074 // monitor file 0075 mInotifyFd = inotify_init(); 0076 if (mInotifyFd > 0) { 0077 (void)fcntl(mInotifyFd, F_SETFD, FD_CLOEXEC); 0078 mInotifyWatch = inotify_add_watch(mInotifyFd, mUrl.path().toLatin1().constData(), IN_CLOSE | IN_OPEN | IN_MODIFY | IN_ATTRIB); 0079 if (mInotifyWatch >= 0) { 0080 auto sn = new QSocketNotifier(mInotifyFd, QSocketNotifier::Read, this); 0081 connect(sn, &QSocketNotifier::activated, this, &EditorWatcher::inotifyEvent); 0082 mHaveInotify = true; 0083 mFileModified = false; 0084 } 0085 } else { 0086 qCWarning(MESSAGECOMPOSER_LOG()) << "Failed to activate INOTIFY!"; 0087 } 0088 #endif 0089 0090 // start the editor 0091 KIO::DesktopExecParser parser(*offer, list); 0092 parser.setUrlsAreTempFiles(false); 0093 const QStringList params = parser.resultingArguments(); 0094 mEditor = new KProcess(this); 0095 mEditor->setProgram(params); 0096 connect(mEditor, &KProcess::finished, this, &EditorWatcher::editorExited); 0097 mEditor->start(); 0098 if (!mEditor->waitForStarted()) { 0099 return CannotStart; 0100 } 0101 mEditorRunning = true; 0102 0103 mEditTime.start(); 0104 return NoError; 0105 } 0106 0107 bool EditorWatcher::fileChanged() const 0108 { 0109 return mFileModified; 0110 } 0111 0112 QUrl EditorWatcher::url() const 0113 { 0114 return mUrl; 0115 } 0116 0117 void EditorWatcher::inotifyEvent() 0118 { 0119 assert(mHaveInotify); 0120 0121 #ifdef HAVE_SYS_INOTIFY_H 0122 int pending = -1; 0123 int offsetStartRead = 0; // where we read into buffer 0124 char buf[8192]; 0125 assert(mInotifyFd > -1); 0126 ioctl(mInotifyFd, FIONREAD, &pending); 0127 0128 while (pending > 0) { 0129 const int bytesToRead = qMin(pending, (int)sizeof(buf) - offsetStartRead); 0130 0131 int bytesAvailable = read(mInotifyFd, &buf[offsetStartRead], bytesToRead); 0132 pending -= bytesAvailable; 0133 bytesAvailable += offsetStartRead; 0134 offsetStartRead = 0; 0135 0136 int offsetCurrent = 0; 0137 while (bytesAvailable >= (int)sizeof(struct inotify_event)) { 0138 const struct inotify_event *const event = (struct inotify_event *)&buf[offsetCurrent]; 0139 const int eventSize = sizeof(struct inotify_event) + event->len; 0140 if (bytesAvailable < eventSize) { 0141 break; 0142 } 0143 0144 bytesAvailable -= eventSize; 0145 offsetCurrent += eventSize; 0146 if (event->mask & IN_OPEN) { 0147 mFileOpen = true; 0148 } 0149 if (event->mask & IN_CLOSE) { 0150 mFileOpen = false; 0151 } 0152 if (event->mask & (IN_MODIFY | IN_ATTRIB)) { 0153 mFileModified = true; 0154 } 0155 } 0156 if (bytesAvailable > 0) { 0157 // copy partial event to beginning of buffer 0158 memmove(buf, &buf[offsetCurrent], bytesAvailable); 0159 offsetStartRead = bytesAvailable; 0160 } 0161 } 0162 #endif 0163 mTimer.start(500); 0164 } 0165 0166 void EditorWatcher::editorExited() 0167 { 0168 mEditorRunning = false; 0169 mTimer.start(500); 0170 } 0171 0172 void EditorWatcher::checkEditDone() 0173 { 0174 if (mEditorRunning || (mFileOpen && mHaveInotify) || mDone) { 0175 return; 0176 } 0177 0178 static QStringList readOnlyMimeTypes; 0179 if (readOnlyMimeTypes.isEmpty()) { 0180 readOnlyMimeTypes << QStringLiteral("message/rfc822") << QStringLiteral("application/pdf"); 0181 } 0182 0183 // protect us against double-deletion by calling this method again while 0184 // the subeventloop of the message box is running 0185 mDone = true; 0186 0187 // check if it's a mime type that's mostly handled read-only 0188 const bool isReadOnlyMimeType = (readOnlyMimeTypes.contains(mMimeType) || mMimeType.startsWith(QLatin1StringView("image/"))); 0189 0190 // nobody can edit that fast, we seem to be unable to detect 0191 // when the editor will be closed 0192 if (mEditTime.elapsed() <= 3000 && !isReadOnlyMimeType) { 0193 KMessageBox::information(mParentWidget, 0194 i18n("KMail is unable to detect when the chosen editor is closed. " 0195 "To avoid data loss, editing the attachment will be aborted."), 0196 i18nc("@title:window", "Unable to edit attachment"), 0197 QStringLiteral("UnableToEditAttachment")); 0198 } 0199 0200 Q_EMIT editDone(this); 0201 deleteLater(); 0202 } 0203 0204 #include "moc_editorwatcher.cpp"