| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2017 Elvis Angelaccio <elvis.angelaccio@kde.org> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
| 6 | */ |
| 7 | |
| 8 | #include "kioexecd.h" |
| 9 | #include "kioexecdadaptor.h" |
| 10 | #include "kioexecdebug.h" |
| 11 | |
| 12 | #include <KDirWatch> |
| 13 | #include <KIO/CopyJob> |
| 14 | #include <KLocalizedString> |
| 15 | #include <KMessageBox> |
| 16 | #include <KPluginFactory> |
| 17 | |
| 18 | #include <QDir> |
| 19 | #include <QFileInfo> |
| 20 | #include <QStandardPaths> |
| 21 | |
| 22 | static const int predefinedTimeout = 30000; // 30s |
| 23 | |
| 24 | K_PLUGIN_CLASS_WITH_JSON(KIOExecd, "kioexecd.json" ) |
| 25 | |
| 26 | KIOExecd::KIOExecd(QObject *parent, const QList<QVariant> &) |
| 27 | : KDEDModule(parent) |
| 28 | { |
| 29 | qCDebug(KIOEXEC) << "kioexecd started" ; |
| 30 | |
| 31 | new KIOExecdAdaptor(this); |
| 32 | m_watcher = new KDirWatch(this); |
| 33 | |
| 34 | connect(sender: m_watcher, signal: &KDirWatch::dirty, context: this, slot: &KIOExecd::slotDirty); |
| 35 | connect(sender: m_watcher, signal: &KDirWatch::created, context: this, slot: &KIOExecd::slotCreated); |
| 36 | connect(sender: m_watcher, signal: &KDirWatch::deleted, context: this, slot: &KIOExecd::slotDeleted); |
| 37 | m_timer.setSingleShot(true); |
| 38 | m_timer.setInterval(predefinedTimeout); |
| 39 | connect(sender: &m_timer, signal: &QTimer::timeout, context: this, slot: &KIOExecd::slotCheckDeletedFiles); |
| 40 | } |
| 41 | |
| 42 | KIOExecd::~KIOExecd() |
| 43 | { |
| 44 | // Remove the remaining temporary files and if possible their parent directories |
| 45 | for (auto it = m_watched.constBegin(); it != m_watched.constEnd(); ++it) { |
| 46 | QFileInfo info(it.key()); |
| 47 | const auto parentDir = info.path(); |
| 48 | qCDebug(KIOEXEC) << "About to delete" << parentDir << "containing" << info.fileName(); |
| 49 | QFile::remove(fileName: it.key()); |
| 50 | QDir().rmdir(dirName: parentDir); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | void KIOExecd::watch(const QString &path, const QString &destUrl) |
| 55 | { |
| 56 | if (m_watched.contains(key: path)) { |
| 57 | qCDebug(KIOEXEC) << "Already watching" << path; |
| 58 | return; |
| 59 | } |
| 60 | |
| 61 | qCDebug(KIOEXEC) << "Going to watch" << path << "for changes, remote destination is" << destUrl; |
| 62 | |
| 63 | // Watch the temporary file for modifications, creations or deletions |
| 64 | m_watcher->addFile(file: path); |
| 65 | m_watched.insert(key: path, value: QUrl(destUrl)); |
| 66 | } |
| 67 | |
| 68 | void KIOExecd::slotCreated(const QString &path) |
| 69 | { |
| 70 | m_deleted.remove(key: path); |
| 71 | |
| 72 | // When the file is recreated, it is not signaled as dirty. |
| 73 | slotDirty(path); |
| 74 | } |
| 75 | |
| 76 | void KIOExecd::slotDirty(const QString &path) |
| 77 | { |
| 78 | if (!m_watched.contains(key: path)) { |
| 79 | return; |
| 80 | } |
| 81 | |
| 82 | const auto dest = m_watched.value(key: path); |
| 83 | |
| 84 | const auto result = KMessageBox::questionTwoActions( |
| 85 | parent: nullptr, |
| 86 | xi18nc("@info" , "The file <filename>%1</filename><nl/>has been modified. Do you want to upload the changes?" , dest.toDisplayString()), |
| 87 | i18n("File Changed" ), |
| 88 | primaryAction: KGuiItem(i18n("Upload" ), QLatin1String("cloud-upload-symbolic" )), |
| 89 | secondaryAction: KGuiItem(i18n("Do Not Upload" ), QLatin1String("dialog-cancel-symbolic" ))); |
| 90 | if (result != KMessageBox::PrimaryAction) { |
| 91 | return; |
| 92 | } |
| 93 | |
| 94 | qCDebug(KIOEXEC) << "Uploading" << path << "to" << dest; |
| 95 | auto job = KIO::copy(src: QUrl::fromLocalFile(localfile: path), dest); |
| 96 | connect(sender: job, signal: &KJob::result, context: this, slot: [](KJob *job) { |
| 97 | if (job->error()) { |
| 98 | KMessageBox::error(parent: nullptr, text: job->errorString()); |
| 99 | } |
| 100 | }); |
| 101 | } |
| 102 | |
| 103 | void KIOExecd::slotDeleted(const QString &path) |
| 104 | { |
| 105 | if (!m_watched.contains(key: path)) { |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | m_deleted.insert(key: path, value: QDateTime::currentDateTimeUtc()); |
| 110 | m_timer.start(); |
| 111 | } |
| 112 | |
| 113 | void KIOExecd::slotCheckDeletedFiles() |
| 114 | { |
| 115 | const QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); |
| 116 | // check if the deleted (and not recreated) files where deleted 30s ago or more |
| 117 | for (auto it = m_deleted.begin(); it != m_deleted.end();) { |
| 118 | if (it.value().msecsTo(currentDateTime) >= predefinedTimeout) { |
| 119 | qCDebug(KIOEXEC) << "Going to forget" << it.key(); |
| 120 | m_watcher->removeFile(file: it.key()); |
| 121 | m_watched.remove(key: it.key()); |
| 122 | QFileInfo info(it.key()); |
| 123 | const auto parentDir = info.path(); |
| 124 | qCDebug(KIOEXEC) << "About to delete" << parentDir; |
| 125 | QDir().rmdir(dirName: parentDir); |
| 126 | it = m_deleted.erase(it); |
| 127 | } else { |
| 128 | ++it; |
| 129 | } |
| 130 | } |
| 131 | if (!m_deleted.isEmpty()) { |
| 132 | m_timer.start(); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | #include "kioexecd.moc" |
| 137 | #include "moc_kioexecd.cpp" |
| 138 | |