File indexing completed on 2024-04-21 04:56:54

0001 /**
0002  * SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "shareplugin.h"
0008 
0009 #include <QDBusConnection>
0010 #include <QDateTime>
0011 #include <QDesktopServices>
0012 #include <QDir>
0013 #include <QMimeData>
0014 #include <QProcess>
0015 #include <QStandardPaths>
0016 #include <QTemporaryFile>
0017 
0018 #include <KApplicationTrader>
0019 #include <KIO/Job>
0020 #include <KIO/MkpathJob>
0021 #include <KJobTrackerInterface>
0022 #include <KLocalizedString>
0023 #include <KNotification>
0024 #include <KPluginFactory>
0025 #include <KSystemClipboard>
0026 
0027 #include "core/daemon.h"
0028 #include "core/filetransferjob.h"
0029 #include "plugin_share_debug.h"
0030 
0031 K_PLUGIN_CLASS_WITH_JSON(SharePlugin, "kdeconnect_share.json")
0032 
0033 SharePlugin::SharePlugin(QObject *parent, const QVariantList &args)
0034     : KdeConnectPlugin(parent, args)
0035     , m_compositeJob()
0036 {
0037 }
0038 
0039 QUrl SharePlugin::destinationDir() const
0040 {
0041     const QString defaultDownloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
0042     QUrl dir = QUrl::fromLocalFile(config()->getString(QStringLiteral("incoming_path"), defaultDownloadPath));
0043 
0044     if (dir.path().contains(QLatin1String("%1"))) {
0045         dir.setPath(dir.path().arg(device()->name()));
0046     }
0047 
0048     KJob *job = KIO::mkpath(dir);
0049     bool ret = job->exec();
0050     if (!ret) {
0051         qWarning() << "couldn't create" << dir;
0052     }
0053 
0054     return dir;
0055 }
0056 
0057 QUrl SharePlugin::getFileDestination(const QString filename) const
0058 {
0059     const QUrl dir = destinationDir().adjusted(QUrl::StripTrailingSlash);
0060     QUrl destination(dir);
0061     destination.setPath(dir.path() + QStringLiteral("/") + filename, QUrl::DecodedMode);
0062     return destination;
0063 }
0064 
0065 static QString cleanFilename(const QString &filename)
0066 {
0067     int idx = filename.lastIndexOf(QLatin1Char('/'));
0068     return idx >= 0 ? filename.mid(idx + 1) : filename;
0069 }
0070 
0071 void SharePlugin::setDateModified(const QUrl &destination, const qint64 timestamp)
0072 {
0073     QFile receivedFile(destination.toLocalFile());
0074     if (!receivedFile.exists() || !receivedFile.open(QIODevice::ReadWrite | QIODevice::Text)) {
0075         return;
0076     }
0077     receivedFile.setFileTime(QDateTime::fromMSecsSinceEpoch(timestamp), QFileDevice::FileTime(QFileDevice::FileModificationTime));
0078 }
0079 
0080 void SharePlugin::setDateCreated(const QUrl &destination, const qint64 timestamp)
0081 {
0082     QFile receivedFile(destination.toLocalFile());
0083     if (!receivedFile.exists() || !receivedFile.open(QIODevice::ReadWrite | QIODevice::Text)) {
0084         return;
0085     }
0086     receivedFile.setFileTime(QDateTime::fromMSecsSinceEpoch(timestamp), QFileDevice::FileTime(QFileDevice::FileBirthTime));
0087 }
0088 
0089 void SharePlugin::receivePacket(const NetworkPacket &np)
0090 {
0091     /*
0092         //TODO: Write a test like this
0093         if (np.type() == PACKET_TYPE_PING) {
0094 
0095             qCDebug(KDECONNECT_PLUGIN_SHARE) << "sending file" << (QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.bashrc");
0096 
0097             NetworkPacket out(PACKET_TYPE_SHARE_REQUEST);
0098             out.set("filename", mDestinationDir + "itworks.txt");
0099             AutoClosingQFile* file = new AutoClosingQFile(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.bashrc"); //Test file to
0100        transfer
0101 
0102             out.setPayload(file, file->size());
0103 
0104             device()->sendPacket(out);
0105 
0106         }
0107     */
0108 
0109     qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer";
0110 
0111     if (np.hasPayload() || np.has(QStringLiteral("filename"))) {
0112         //         qCDebug(KDECONNECT_PLUGIN_SHARE) << "receiving file" << filename << "in" << dir << "into" << destination;
0113         const QString filename = cleanFilename(np.get<QString>(QStringLiteral("filename"), QString::number(QDateTime::currentMSecsSinceEpoch())));
0114         QUrl destination = getFileDestination(filename);
0115 
0116         if (np.hasPayload()) {
0117             qint64 dateCreated = np.get<qint64>(QStringLiteral("creationTime"), QDateTime::currentMSecsSinceEpoch());
0118             qint64 dateModified = np.get<qint64>(QStringLiteral("lastModified"), QDateTime::currentMSecsSinceEpoch());
0119             const bool open = np.get<bool>(QStringLiteral("open"), false);
0120 
0121             if (!m_compositeJob) {
0122                 m_compositeJob = new CompositeFileTransferJob(device()->id());
0123                 m_compositeJob->setProperty("destUrl", destinationDir().toString());
0124                 m_compositeJob->setProperty("immediateProgressReporting", true);
0125                 Daemon::instance()->jobTracker()->registerJob(m_compositeJob);
0126             }
0127 
0128             FileTransferJob *job = np.createPayloadTransferJob(destination);
0129             job->setOriginName(device()->name() + QStringLiteral(": ") + filename);
0130             job->setAutoRenameIfDestinatinonExists(true);
0131             connect(job, &KJob::result, this, [this, dateCreated, dateModified, open](KJob *job) -> void {
0132                 finished(job, dateCreated, dateModified, open);
0133             });
0134             m_compositeJob->addSubjob(job);
0135 
0136             if (!m_compositeJob->isRunning()) {
0137                 m_compositeJob->start();
0138             }
0139         } else {
0140             QFile file(destination.toLocalFile());
0141             file.open(QIODevice::WriteOnly);
0142             file.close();
0143         }
0144     } else if (np.has(QStringLiteral("text"))) {
0145         QString text = np.get<QString>(QStringLiteral("text"));
0146 
0147         auto mimeData = new QMimeData;
0148         mimeData->setText(text);
0149         KSystemClipboard::instance()->setMimeData(mimeData, QClipboard::Clipboard);
0150 
0151         QUrl url;
0152         QStringList lines = text.split(QStringLiteral("\n"), Qt::SkipEmptyParts);
0153         if (lines.count()) {
0154             url.setUrl(lines[lines.count() - 1].trimmed());
0155         }
0156 
0157         KNotification *notif = new KNotification(QStringLiteral("textShareReceived"));
0158         notif->setComponentName(QStringLiteral("kdeconnect"));
0159         notif->setText(text);
0160         notif->setTitle(i18nc("@info Some piece of text was received from a connected device", "Shared text from %1 copied to clipboard", device()->name()));
0161 
0162         auto openTextEditor = [this, text] {
0163             KService::Ptr service = KApplicationTrader::preferredService(QStringLiteral("text/plain"));
0164             const QString defaultApp = service ? service->desktopEntryName() : QString();
0165 
0166             if (defaultApp == QLatin1String("org.kde.kate") || defaultApp == QLatin1String("org.kde.kwrite")) {
0167                 QProcess *proc = new QProcess();
0168                 connect(proc, &QProcess::finished, proc, &QObject::deleteLater);
0169                 proc->start(defaultApp.section(QStringLiteral("."), 2, 2), QStringList(QStringLiteral("--stdin")));
0170                 proc->write(text.toUtf8());
0171                 proc->closeWriteChannel();
0172             } else {
0173                 QTemporaryFile tmpFile;
0174                 tmpFile.setFileTemplate(QStringLiteral("kdeconnect-XXXXXX.txt"));
0175                 tmpFile.setAutoRemove(false);
0176                 tmpFile.open();
0177                 tmpFile.write(text.toUtf8());
0178                 tmpFile.close();
0179 
0180                 const QString fileName = tmpFile.fileName();
0181                 QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
0182                 Q_EMIT shareReceived(fileName);
0183             }
0184         };
0185 
0186         auto openUrl = [this, url] {
0187             QDesktopServices::openUrl(url);
0188             Q_EMIT shareReceived(url.toString());
0189         };
0190 
0191 #if QT_VERSION_MAJOR == 6
0192         KNotificationAction *textEditorAction = notif->addAction(i18nc("@action:button Edit text with default text editor", "Open in Text Editor"));
0193         connect(textEditorAction, &KNotificationAction::activated, this, openTextEditor);
0194 
0195         if (url.isValid() && (url.scheme() == QStringLiteral("http") || url.scheme() == QStringLiteral("https"))) {
0196             KNotificationAction *openLinkAction = notif->addAction(i18nc("@action:button Open URL with default app", "Open Link"));
0197             connect(openLinkAction, &KNotificationAction::activated, this, openUrl);
0198         }
0199 #else
0200         QStringList actions;
0201         actions << i18nc("@action:button Edit text with default text editor", "Open in Text Editor");
0202         if (url.isValid() && (url.scheme() == QStringLiteral("http") || url.scheme() == QStringLiteral("https"))) {
0203             actions << i18nc("@action:button Open URL with default app", "Open Link");
0204         }
0205         notif->setActions(actions);
0206 
0207         connect(notif, &KNotification::action1Activated, this, openTextEditor);
0208 
0209         connect(notif, &KNotification::action2Activated, this, openUrl);
0210 #endif
0211 
0212         notif->sendEvent();
0213 
0214     } else if (np.has(QStringLiteral("url"))) {
0215         QUrl url = QUrl::fromEncoded(np.get<QByteArray>(QStringLiteral("url")));
0216         QDesktopServices::openUrl(url);
0217         Q_EMIT shareReceived(url.toString());
0218     } else {
0219         qCDebug(KDECONNECT_PLUGIN_SHARE) << "Error: Nothing attached!";
0220     }
0221 }
0222 
0223 void SharePlugin::finished(KJob *job, const qint64 dateModified, const qint64 dateCreated, const bool open)
0224 {
0225     FileTransferJob *ftjob = qobject_cast<FileTransferJob *>(job);
0226     if (ftjob && !job->error()) {
0227         Q_EMIT shareReceived(ftjob->destination().toString());
0228         setDateCreated(ftjob->destination(), dateCreated);
0229         setDateModified(ftjob->destination(), dateModified);
0230         qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished." << ftjob->destination();
0231         if (open) {
0232             QDesktopServices::openUrl(ftjob->destination());
0233         }
0234     } else {
0235         qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer failed." << (ftjob ? ftjob->destination() : QUrl());
0236     }
0237 }
0238 
0239 void SharePlugin::openDestinationFolder()
0240 {
0241     QDesktopServices::openUrl(destinationDir());
0242 }
0243 
0244 void SharePlugin::shareUrl(const QUrl &url, bool open)
0245 {
0246     NetworkPacket packet(PACKET_TYPE_SHARE_REQUEST);
0247     if (url.isLocalFile()) {
0248         QSharedPointer<QFile> ioFile(new QFile(url.toLocalFile()));
0249 
0250         if (!ioFile->exists()) {
0251             Daemon::instance()->reportError(i18n("Could not share file"), i18n("%1 does not exist", url.toLocalFile()));
0252             return;
0253         } else {
0254             QFileInfo info(*ioFile);
0255             packet.setPayload(ioFile, ioFile->size());
0256             packet.set<QString>(QStringLiteral("filename"), QUrl(url).fileName());
0257             packet.set<qint64>(QStringLiteral("creationTime"), info.birthTime().toMSecsSinceEpoch());
0258             packet.set<qint64>(QStringLiteral("lastModified"), info.lastModified().toMSecsSinceEpoch());
0259             packet.set<bool>(QStringLiteral("open"), open);
0260         }
0261     } else {
0262         packet.set<QString>(QStringLiteral("url"), url.toString());
0263     }
0264     sendPacket(packet);
0265 }
0266 
0267 void SharePlugin::shareUrls(const QStringList &urls)
0268 {
0269     for (const QString &url : urls) {
0270         shareUrl(QUrl(url), false);
0271     }
0272 }
0273 
0274 void SharePlugin::shareText(const QString &text)
0275 {
0276     NetworkPacket packet(PACKET_TYPE_SHARE_REQUEST);
0277     packet.set<QString>(QStringLiteral("text"), text);
0278     sendPacket(packet);
0279 }
0280 
0281 QString SharePlugin::dbusPath() const
0282 {
0283     return QLatin1String("/modules/kdeconnect/devices/%1/share").arg(device()->id());
0284 }
0285 
0286 #include "moc_shareplugin.cpp"
0287 #include "shareplugin.moc"