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