diff --git a/core/backends/lan/compositeuploadjob.cpp b/core/backends/lan/compositeuploadjob.cpp index 3d38fdce..619901c1 100644 --- a/core/backends/lan/compositeuploadjob.cpp +++ b/core/backends/lan/compositeuploadjob.cpp @@ -1,275 +1,292 @@ /** * Copyright 2018 Erik Duisters * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "compositeuploadjob.h" #include #include #include #include #include "lanlinkprovider.h" #include +#include "plugins/share/shareplugin.h" CompositeUploadJob::CompositeUploadJob(const QString& deviceId, bool displayNotification) : KCompositeJob() , m_server(new Server(this)) , m_socket(nullptr) , m_port(0) , m_deviceId(deviceId) , m_running(false) , m_currentJobNum(1) , m_totalJobs(0) , m_currentJobSendPayloadSize(0) , m_totalSendPayloadSize(0) , m_totalPayloadSize(0) , m_currentJob(nullptr) , m_prevElapsedTime(0) + , m_updatePacketPending(false) { setCapabilities(Killable); if (displayNotification) { KIO::getJobTracker()->registerJob(this); } } bool CompositeUploadJob::isRunning() { return m_running; } void CompositeUploadJob::start() { if (m_running) { qCWarning(KDECONNECT_CORE) << "CompositeUploadJob::start() - already running"; return; } if (!hasSubjobs()) { qCWarning(KDECONNECT_CORE) << "CompositeUploadJob::start() - there are no subjobs to start"; emitResult(); return; } if (!startListening()) { return; } connect(m_server, &QTcpServer::newConnection, this, &CompositeUploadJob::newConnection); m_running = true; //Give SharePlugin some time to add subjobs QMetaObject::invokeMethod(this, "startNextSubJob", Qt::QueuedConnection); } bool CompositeUploadJob::startListening() { m_port = MIN_PORT; while (!m_server->listen(QHostAddress::Any, m_port)) { m_port++; if (m_port > MAX_PORT) { //No ports available? qCWarning(KDECONNECT_CORE) << "CompositeUploadJob::startListening() - Error opening a port in range" << MIN_PORT << "-" << MAX_PORT; m_port = 0; setError(NoPortAvailable); setErrorText(i18n("Couldn't find an available port")); emitResult(); return false; } } qCDebug(KDECONNECT_CORE) << "CompositeUploadJob::startListening() - listening on port: " << m_port; return true; } void CompositeUploadJob::startNextSubJob() { m_currentJob = qobject_cast(subjobs().at(0)); m_currentJobSendPayloadSize = 0; emitDescription(m_currentJob->getNetworkPacket().get(QStringLiteral("filename"))); connect(m_currentJob, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(slotProcessedAmount(KJob*,KJob::Unit,qulonglong))); //Already done by KCompositeJob //connect(m_currentJob, &KJob::result, this, &CompositeUploadJob::slotResult); //TODO: Create a copy of the networkpacket that can be re-injected if sending via lan fails? NetworkPacket np = m_currentJob->getNetworkPacket(); np.setPayload(nullptr, np.payloadSize()); np.setPayloadTransferInfo({{"port", m_port}}); np.set(QStringLiteral("numberOfFiles"), m_totalJobs); np.set(QStringLiteral("totalPayloadSize"), m_totalPayloadSize); if (Daemon::instance()->getDevice(m_deviceId)->sendPacket(np)) { m_server->resumeAccepting(); } else { setError(SendingNetworkPacketFailed); setErrorText(i18n("Failed to send packet to %1", Daemon::instance()->getDevice(m_deviceId)->name())); emitResult(); } } void CompositeUploadJob::newConnection() { m_server->pauseAccepting(); m_socket = m_server->nextPendingConnection(); if (!m_socket) { qCDebug(KDECONNECT_CORE) << "CompositeUploadJob::newConnection() - m_server->nextPendingConnection() returned a nullptr"; return; } m_currentJob->setSocket(m_socket); connect(m_socket, &QSslSocket::disconnected, this, &CompositeUploadJob::socketDisconnected); connect(m_socket, QOverload::of(&QAbstractSocket::error), this, &CompositeUploadJob::socketError); connect(m_socket, QOverload &>::of(&QSslSocket::sslErrors), this, &CompositeUploadJob::sslError); connect(m_socket, &QSslSocket::encrypted, this, &CompositeUploadJob::encrypted); LanLinkProvider::configureSslSocket(m_socket, m_deviceId, true); m_socket->startServerEncryption(); } void CompositeUploadJob::socketDisconnected() { m_socket->close(); } void CompositeUploadJob::socketError(QAbstractSocket::SocketError error) { Q_UNUSED(error); //Do not close the socket because when android closes the socket (share is cancelled) closing the socket leads to a cyclic socketError and eventually a segv setError(SocketError); emitResult(); m_running = false; } void CompositeUploadJob::sslError(const QList& errors) { Q_UNUSED(errors); m_socket->close(); setError(SslError); emitResult(); m_running = false; } void CompositeUploadJob::encrypted() { if (!m_timer.isValid()) { m_timer.start(); } m_currentJob->start(); } bool CompositeUploadJob::addSubjob(KJob* job) { if (UploadJob *uploadJob = qobject_cast(job)) { NetworkPacket np = uploadJob->getNetworkPacket(); m_totalJobs++; if (np.payloadSize() >= 0 ) { m_totalPayloadSize += np.payloadSize(); setTotalAmount(Bytes, m_totalPayloadSize); } QString filename; QString filenameArg = QStringLiteral("filename"); if (m_currentJob) { filename = m_currentJob->getNetworkPacket().get(filenameArg); } else { filename = np.get(filenameArg); } emitDescription(filename); + if (m_running && !m_updatePacketPending) { + m_updatePacketPending = true; + QMetaObject::invokeMethod(this, "sendUpdatePacket", Qt::QueuedConnection); + } + return KCompositeJob::addSubjob(job); } else { qCDebug(KDECONNECT_CORE) << "CompositeUploadJob::addSubjob() - you can only add UploadJob's, ignoring"; return false; } } +void CompositeUploadJob::sendUpdatePacket() { + NetworkPacket np(PACKET_TYPE_SHARE_REQUEST_UPDATE); + np.set(QStringLiteral("numberOfFiles"), m_totalJobs); + np.set(QStringLiteral("totalPayloadSize"), m_totalPayloadSize); + + Daemon::instance()->getDevice(m_deviceId)->sendPacket(np); + + m_updatePacketPending = false; +} + bool CompositeUploadJob::doKill() { if (m_running) { m_running = false; return m_currentJob->stop(); } return true; } void CompositeUploadJob::slotProcessedAmount(KJob *job, KJob::Unit unit, qulonglong amount) { Q_UNUSED(job); m_currentJobSendPayloadSize = amount; quint64 uploaded = m_totalSendPayloadSize + m_currentJobSendPayloadSize; if (uploaded == m_totalPayloadSize || m_prevElapsedTime == 0 || m_timer.elapsed() - m_prevElapsedTime >= 100) { m_prevElapsedTime = m_timer.elapsed(); setProcessedAmount(unit, uploaded); const auto elapsed = m_timer.elapsed(); if (elapsed > 0) { emitSpeed((1000 * uploaded) / elapsed); } } } void CompositeUploadJob::slotResult(KJob *job) { //Copies job error and errorText and emits result if job is in error otherwise removes job from subjob list KCompositeJob::slotResult(job); if (error() || !m_running) { return; } m_totalSendPayloadSize += m_currentJobSendPayloadSize; if (hasSubjobs()) { m_currentJobNum++; startNextSubJob(); } else { Q_EMIT description(this, i18n("Finished sending to %1", Daemon::instance()->getDevice(this->m_deviceId)->name()), { QStringLiteral(""), i18np("Sent 1 file", "Sent %1 files", m_totalJobs) } ); emitResult(); } } void CompositeUploadJob::emitDescription(const QString& currentFileName) { QPair field2; if (m_totalJobs > 1) { field2.first = i18n("Progress"); field2.second = i18n("Sending file %1 of %2", m_currentJobNum, m_totalJobs); } Q_EMIT description(this, i18n("Sending to %1", Daemon::instance()->getDevice(this->m_deviceId)->name()), { i18n("File"), currentFileName }, field2 ); } diff --git a/core/backends/lan/compositeuploadjob.h b/core/backends/lan/compositeuploadjob.h index d0a2d01e..d5aa3089 100644 --- a/core/backends/lan/compositeuploadjob.h +++ b/core/backends/lan/compositeuploadjob.h @@ -1,85 +1,87 @@ /** * Copyright 2018 Erik Duisters * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef COMPOSITEUPLOADJOB_H #define COMPOSITEUPLOADJOB_H #include "kdeconnectcore_export.h" #include #include "server.h" #include "uploadjob.h" class KDECONNECTCORE_EXPORT CompositeUploadJob : public KCompositeJob { Q_OBJECT public: explicit CompositeUploadJob(const QString& deviceId, bool displayNotification); void start() override; QVariantMap transferInfo(); bool isRunning(); bool addSubjob(KJob* job) override; private: bool startListening(); void emitDescription(const QString& currentFileName); protected: bool doKill() override; private: enum { NoPortAvailable = UserDefinedError, SendingNetworkPacketFailed, SocketError, SslError }; Server *const m_server; QSslSocket *m_socket; quint16 m_port; const QString& m_deviceId; bool m_running; int m_currentJobNum; int m_totalJobs; quint64 m_currentJobSendPayloadSize; quint64 m_totalSendPayloadSize; quint64 m_totalPayloadSize; UploadJob *m_currentJob; QElapsedTimer m_timer; quint64 m_prevElapsedTime; + bool m_updatePacketPending; const static quint16 MIN_PORT = 1739; const static quint16 MAX_PORT = 1764; private Q_SLOTS: void newConnection(); void socketDisconnected(); void socketError(QAbstractSocket::SocketError socketError); void sslError(const QList& errors); void encrypted(); void slotProcessedAmount(KJob *job, KJob::Unit unit, qulonglong amount); void slotResult(KJob *job) override; void startNextSubJob(); + void sendUpdatePacket(); }; #endif //COMPOSITEUPLOADJOB_H diff --git a/plugins/share/kdeconnect_share.json b/plugins/share/kdeconnect_share.json index 1a23ad6a..75ad9648 100644 --- a/plugins/share/kdeconnect_share.json +++ b/plugins/share/kdeconnect_share.json @@ -1,131 +1,132 @@ { "KPlugin": { "Authors": [ { "Email": "albertvaka@gmail.com", "Name": "Albert Vaca", "Name[ar]": "Albert Vaca", "Name[ca@valencia]": "Albert Vaca", "Name[ca]": "Albert Vaca", "Name[cs]": "Albert Vaca", "Name[da]": "Albert Vaca", "Name[de]": "Albert Vaca", "Name[el]": "Albert Vaca", "Name[en_GB]": "Albert Vaca", "Name[es]": "Albert Vaca", "Name[et]": "Albert Vaca", "Name[eu]": "Albert Vaca", "Name[fi]": "Albert Vaca", "Name[fr]": "Albert Vaca", "Name[gl]": "Albert Vaca", "Name[id]": "Albert Vaca", "Name[it]": "Albert Vaca", "Name[ko]": "Albert Vaca", "Name[nl]": "Albert Vaca", "Name[nn]": "Albert Vaca", "Name[pl]": "Albert Vaca", "Name[pt]": "Albert Vaca", "Name[pt_BR]": "Albert Vaca", "Name[ru]": "Albert Vaca", "Name[sk]": "Albert Vaca", "Name[sr@ijekavian]": "Алберт Вака Синтора", "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", "Name[sr@latin]": "Albert Vaka Sintora", "Name[sr]": "Алберт Вака Синтора", "Name[sv]": "Albert Vaca", "Name[tr]": "Albert Vaca", "Name[uk]": "Albert Vaca", "Name[x-test]": "xxAlbert Vacaxx", "Name[zh_CN]": "Albert Vaca", "Name[zh_TW]": "Albert Vaca" } ], "Description": "Receive and send files, URLs or plain text easily", "Description[ar]": "استقبل الملفّات أو العناوين أو النّصوص الصّرفة وأرسلها بسهولة", "Description[ca@valencia]": "Rep i envia fitxers, URL o text pla amb facilitat", "Description[ca]": "Rep i envia fitxers, URL o text pla amb facilitat", "Description[cs]": "Snadno přijímejte a posílejte soubory, URL nebo čistý text", "Description[da]": "Modtag og send nemt filer, URL'er eller klartekst", "Description[de]": "Empfang und Senden von Dateien, URLs oder einfachem Text", "Description[el]": "Εύκολη λήψη και αποστολή αρχείων, URL ή απλού κειμένου", "Description[en_GB]": "Receive and send files, URLs or plain text easily", "Description[es]": "Recibir y enviar archivos, URL o texto sin formato fácilmente", "Description[et]": "Failide, URL-ide või lihtteksti hõlpus vastuvõtmine ja saatmine", "Description[eu]": "Jaso eta bidali fitxategiak, URL-ak, edo testu soila erraz", "Description[fi]": "Vastaanota ja lähetä tiedostoja, verkko-osoitteita tai tekstiä helposti", "Description[fr]": "Recevoir et envoyer des fichiers, des URLs ou du texte brut facilement", "Description[gl]": "Comparta e reciba con facilidade ficheiros, enderezos URL ou texto simple.", "Description[hu]": "Fájlok, URL-ek és szöveg fogadása és küldése", "Description[id]": "Terima dan kirim file, URL atau teks plain secara mudah", "Description[it]": "Riceve e invia facilmente file, URL o testo semplice", "Description[ko]": "파일, URL, 일반 텍스트 공유하고 받기", "Description[nl]": "Bestanden, URL's of platte tekst gemakkelijk ontvangen en verzenden", "Description[nn]": "Ta imot og send filer, nettadresser og tekst på ein enkel måte", "Description[pl]": "Udostępniaj i otrzymuj pliki, adresy URL lub zwykły tekst z łatwością", "Description[pt]": "Receber e enviar ficheiros, URL's ou texto normal de forma simples", "Description[pt_BR]": "Recebe e envia facilmente arquivos, URLs ou texto simples", "Description[ru]": "Получение и отправка файлов, URL адресов или простого текста", "Description[sk]": "Prijať a poslať súbory, URL alebo čisté texty jednoducho", "Description[sr@ijekavian]": "Примај и шаљи фајлове, УРЛ‑ове или обичан текст, са лакоћом", "Description[sr@ijekavianlatin]": "Primaj i šalji fajlove, URL‑ove ili običan tekst, sa lakoćom", "Description[sr@latin]": "Primaj i šalji fajlove, URL‑ove ili običan tekst, sa lakoćom", "Description[sr]": "Примај и шаљи фајлове, УРЛ‑ове или обичан текст, са лакоћом", "Description[sv]": "Ta emot och skicka filer, webbadresser eller vanlig text enkelt", "Description[tr]": "Dosyaları, adres ve düz metinleri kolayca alın ve gönderin", "Description[uk]": "Спрощене отримання і надсилання файлів, адрес або текстових фрагментів", "Description[x-test]": "xxReceive and send files, URLs or plain text easilyxx", "Description[zh_CN]": "简单地接收和发送文件,URL 或者文本", "Description[zh_TW]": "接收與傳送檔案,URL網址或純文字檔", "EnabledByDefault": true, "Icon": "folder-network", "Id": "kdeconnect_share", "License": "GPL", "Name": "Share and receive", "Name[ar]": "شارك واستقبل", "Name[ca@valencia]": "Comparteix i rep", "Name[ca]": "Comparteix i rep", "Name[cs]": "Sdílet a přijímat", "Name[da]": "Del og modtag", "Name[de]": "Senden und Empfangen", "Name[el]": "Διαμοιρασμός και λήψη", "Name[en_GB]": "Share and receive", "Name[es]": "Compartir y recibir", "Name[et]": "Jagamine ja vastuvõtmine", "Name[eu]": "Partekatu eta jaso", "Name[fi]": "Jaa ja vastaanota", "Name[fr]": "Partager et recevoir", "Name[gl]": "Compartir e recibir.", "Name[hu]": "Megosztás és fogadás", "Name[id]": "Share and receive", "Name[it]": "Condividi e ricevi", "Name[ko]": "공유하고 받기", "Name[nl]": "Delen en ontvangen", "Name[nn]": "Del og ta imot", "Name[pl]": "Udostępniaj i otrzymuj", "Name[pt]": "Partilhar e receber", "Name[pt_BR]": "Compartilhar e receber", "Name[ru]": "Общий доступ и получение", "Name[sk]": "Zdieľať a prijať", "Name[sr@ijekavian]": "Дели и примај", "Name[sr@ijekavianlatin]": "Deli i primaj", "Name[sr@latin]": "Deli i primaj", "Name[sr]": "Дели и примај", "Name[sv]": "Dela och ta emot", "Name[tr]": "Paylaş ve al", "Name[uk]": "Оприлюднення і отримання", "Name[x-test]": "xxShare and receivexx", "Name[zh_CN]": "分享和接收", "Name[zh_TW]": "共享與接收", "ServiceTypes": [ "KdeConnect/Plugin" ], "Version": "0.1", "Website": "http://albertvaka.wordpress.com" }, "X-KdeConnect-OutgoingPacketType": [ - "kdeconnect.share.request" + "kdeconnect.share.request", + "kdeconnect.share.request.update" ], "X-KdeConnect-SupportedPacketType": [ "kdeconnect.share.request" ] } diff --git a/plugins/share/shareplugin.h b/plugins/share/shareplugin.h index 58c63459..584cb937 100644 --- a/plugins/share/shareplugin.h +++ b/plugins/share/shareplugin.h @@ -1,62 +1,63 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SHAREPLUGIN_H #define SHAREPLUGIN_H #include #include #define PACKET_TYPE_SHARE_REQUEST QStringLiteral("kdeconnect.share.request") +#define PACKET_TYPE_SHARE_REQUEST_UPDATE QStringLiteral("kdeconnect.share.request.update") class SharePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.share") public: explicit SharePlugin(QObject* parent, const QVariantList& args); ///Helper method, QDBus won't recognize QUrl Q_SCRIPTABLE void shareUrl(const QString& url) { shareUrl(QUrl(url)); } Q_SCRIPTABLE void shareUrls(const QStringList& urls); Q_SCRIPTABLE void shareText(const QString& text); Q_SCRIPTABLE void openFile(const QString& file) { openFile(QUrl(file)); } bool receivePacket(const NetworkPacket& np) override; void connected() override {} QString dbusPath() const override; private Q_SLOTS: void finished(KJob*); void openDestinationFolder(); Q_SIGNALS: void shareReceived(const QString& url); private: void shareUrl(const QUrl& url); void openFile(const QUrl& url); QUrl destinationDir() const; QUrl getFileDestination(const QString filename) const; }; #endif