File indexing completed on 2024-04-28 08:49:12
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"