diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a4db750..8be08e3 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -16,6 +16,7 @@ set(squawkCORE_SRC contact.cpp conference.cpp storage.cpp + networkaccess.cpp ) # Tell CMake to create the helloworld executable diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp new file mode 100644 index 0000000..518c7b1 --- /dev/null +++ b/core/networkaccess.cpp @@ -0,0 +1,199 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 "networkaccess.h" + +Core::NetworkAccess::NetworkAccess(QObject* parent): + QObject(parent), + running(false), + manager(), + files("files"), + downloads() +{ +} + +Core::NetworkAccess::~NetworkAccess() +{ + +} + +void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url) +{ + std::map::iterator itr = downloads.find(url); + if (itr != downloads.end()) { + Download* dwn = itr->second; + std::set::const_iterator mItr = dwn->messages.find(messageId); + if (mItr == dwn->messages.end()) { + dwn->messages.insert(messageId); + } + emit downloadFileProgress(messageId, dwn->progress); + } else { + try { + QString path = files.getRecord(url); + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + emit fileLocalPathResponse(messageId, ""); + } + } catch (Archive::NotFound e) { + emit fileLocalPathResponse(messageId, ""); + } catch (Archive::Unknown e) { + qDebug() << "Error requesting file path:" << e.what(); + emit fileLocalPathResponse(messageId, ""); + } + } +} + +void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url) +{ + std::map::iterator itr = downloads.find(url); + if (itr != downloads.end()) { + Download* dwn = itr->second; + std::set::const_iterator mItr = dwn->messages.find(messageId); + if (mItr == dwn->messages.end()) { + dwn->messages.insert(messageId); + } + emit downloadFileProgress(messageId, dwn->progress); + } else { + try { + QString path = files.getRecord(url); + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startDownload(messageId, url); + } + } catch (Archive::NotFound e) { + startDownload(messageId, url); + } catch (Archive::Unknown e) { + qDebug() << "Error requesting file path:" << e.what(); + startDownload(messageId, url); + } + } +} + +void Core::NetworkAccess::start() +{ + if (!running) { + files.open(); + running = true; + } +} + +void Core::NetworkAccess::stop() +{ + if (running) { + files.close(); + running = false; + } +} + +void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = downloads.find(url); + if (itr == downloads.end()) { + qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping"; + } else { + Download* dwn = itr->second; + for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { + emit downloadFileProgress(*mItr, bytesReceived/bytesTotal); + } + } +} + +void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code) +{ + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = downloads.find(url); + if (itr == downloads.end()) { + qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping"; + } else { + itr->second->success = false; + } +} + +void Core::NetworkAccess::onRequestFinished() +{ + QString path(""); + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = downloads.find(url); + if (itr == downloads.end()) { + qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping"; + } else { + Download* dwn = itr->second; + if (dwn->success) { + qDebug() << "download success for" << url; + QStringList hops = url.split("/"); + QString fileName = hops.back(); + QStringList parts = fileName.split("."); + path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + QString suffix(""); + QString realName = parts.front(); + for (QStringList::const_iterator sItr = (parts.begin()++), sEnd = parts.end(); sItr != sEnd; ++sItr) { + suffix += "." + (*sItr); + } + QString postfix(""); + QFileInfo proposedName(path + realName + postfix + suffix); + int counter = 0; + while (proposedName.exists()) { + suffix = QString("(") + std::to_string(++counter).c_str() + ")"; + proposedName = QFileInfo(path + realName + postfix + suffix); + } + + path = proposedName.absoluteFilePath(); + QFile file(path); + if (file.open(QIODevice::WriteOnly)) { + file.write(dwn->reply->readAll()); + file.close(); + files.addRecord(url, path); + qDebug() << "file" << path << "was successfully downloaded"; + } else { + qDebug() << "couldn't save file" << path; + path = ""; + } + } + + for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { + emit fileLocalPathResponse(*mItr, path); + } + + dwn->reply->deleteLater(); + delete dwn; + downloads.erase(itr); + } +} + +void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url) +{ + Download* dwn = new Download({{messageId}, 0, 0, true}); + QNetworkRequest req(url); + dwn->reply = manager.get(req); + connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64))); + connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError))); + connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished())); + downloads.insert(std::make_pair(url, dwn)); + emit downloadFileProgress(messageId, 0); +} + diff --git a/core/networkaccess.h b/core/networkaccess.h new file mode 100644 index 0000000..1d44dbe --- /dev/null +++ b/core/networkaccess.h @@ -0,0 +1,82 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 CORE_NETWORKACCESS_H +#define CORE_NETWORKACCESS_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "storage.h" + +namespace Core { + +/** + * @todo write docs + */ +class NetworkAccess : public QObject +{ + Q_OBJECT + struct Download; +public: + NetworkAccess(QObject* parent = nullptr); + virtual ~NetworkAccess(); + + void start(); + void stop(); + +signals: + void fileLocalPathResponse(const QString& messageId, const QString& path); + void downloadFileProgress(const QString& messageId, qreal value); + +public slots: + void fileLocalPathRequest(const QString& messageId, const QString& url); + void downladFileRequest(const QString& messageId, const QString& url); + +private: + void startDownload(const QString& messageId, const QString& url); + +private slots: + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onRequestError(QNetworkReply::NetworkError code); + void onRequestFinished(); + +private: + bool running; + QNetworkAccessManager manager; + Storage files; + std::map downloads; + + struct Download { + std::set messages; + qreal progress; + QNetworkReply* reply; + bool success; + }; +}; + +} + +#endif // CORE_NETWORKACCESS_H diff --git a/core/squawk.cpp b/core/squawk.cpp index 905dbe5..0468daa 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -26,9 +26,10 @@ Core::Squawk::Squawk(QObject* parent): QObject(parent), accounts(), amap(), - files("files") + network() { - + connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&))); + connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal))); } Core::Squawk::~Squawk() @@ -43,7 +44,7 @@ Core::Squawk::~Squawk() void Core::Squawk::stop() { qDebug("Stopping squawk core.."); - files.close(); + network.stop(); QSettings settings; settings.beginGroup("core"); settings.beginWriteArray("accounts"); @@ -83,7 +84,7 @@ void Core::Squawk::start() } settings.endArray(); settings.endGroup(); - files.open(); + network.start(); } void Core::Squawk::newAccountRequest(const QMap& map) @@ -489,13 +490,10 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url) { - try { - QString path = files.getRecord(url); - emit fileLocalPathResponse(messageId, path); - } catch (Archive::NotFound e) { - emit fileLocalPathResponse(messageId, ""); - } catch (Archive::Unknown e) { - qDebug() << "Error requesting file path:" << e.what(); - emit fileLocalPathResponse(messageId, ""); - } + network.fileLocalPathRequest(messageId, url); +} + +void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url) +{ + network.downladFileRequest(messageId, url); } diff --git a/core/squawk.h b/core/squawk.h index 9208dbf..cdcd474 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -19,7 +19,7 @@ #ifndef CORE_SQUAWK_H #define CORE_SQUAWK_H -#include +#include #include #include #include @@ -28,7 +28,7 @@ #include "account.h" #include "../global.h" -#include "storage.h" +#include "networkaccess.h" namespace Core { @@ -63,6 +63,7 @@ signals: void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); void fileLocalPathResponse(const QString& messageId, const QString& path); + void downloadFileProgress(const QString& messageId, qreal value); public slots: void start(); @@ -84,6 +85,7 @@ public slots: void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); void fileLocalPathRequest(const QString& messageId, const QString& url); + void downloadFileRequest(const QString& messageId, const QString& url); private: typedef std::deque Accounts; @@ -92,7 +94,7 @@ private: Accounts accounts; AccountsMap amap; Shared::Availability state; - Storage files; + NetworkAccess network; private: void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource); diff --git a/main.cpp b/main.cpp index c69ea5c..e4a2618 100644 --- a/main.cpp +++ b/main.cpp @@ -88,6 +88,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, SIGNAL(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)), squawk, SLOT(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool))); QObject::connect(&w, SIGNAL(fileLocalPathRequest(const QString&, const QString&)), squawk, SLOT(fileLocalPathRequest(const QString&, const QString&))); + QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&))); QObject::connect(squawk, SIGNAL(newAccount(const QMap&)), &w, SLOT(newAccount(const QMap&))); QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap&)), @@ -121,6 +122,7 @@ int main(int argc, char *argv[]) QObject::connect(squawk, SIGNAL(removeRoomParticipant(const QString&, const QString&, const QString&)), &w, SLOT(removeRoomParticipant(const QString&, const QString&, const QString&))); QObject::connect(squawk, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), &w, SLOT(fileLocalPathResponse(const QString&, const QString&))); + QObject::connect(squawk, SIGNAL(downloadFileProgress(const QString&, qreal)), &w, SLOT(downloadFileProgress(const QString&, qreal))); //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 596250f..27f279c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -338,7 +338,35 @@ void Squawk::onConversationClosed(QObject* parent) void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url) { - + Conversation* conv = static_cast(sender()); + std::map>::iterator itr = requestedFiles.find(messageId); + bool created = false; + if (itr == requestedFiles.end()) { + itr = requestedFiles.insert(std::make_pair(messageId, std::set())).first; + created = true; + } + itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); + if (created) { + emit downloadFileRequest(messageId, url); + } +} + +void Squawk::downloadFileProgress(const QString& messageId, qreal value) +{ + std::map>::const_iterator itr = requestedFiles.find(messageId); + if (itr == requestedFiles.end()) { + qDebug() << "downloadFileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; + return; + } else { + const std::set& convs = itr->second; + for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { + const Models::Roster::ElId& id = *cItr; + Conversations::const_iterator c = conversations.find(id); + if (c != conversations.end()) { + c->second->responseDownloadProgress(messageId, value); + } + } + } } void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path) @@ -348,7 +376,8 @@ void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping"; return; } else { - const std::set& convs = itr->second; + std::set convs = itr->second; + requestedFiles.erase(itr); for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); @@ -356,7 +385,6 @@ void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path c->second->responseLocalFile(messageId, path); } } - requestedFiles.erase(itr); } } diff --git a/ui/squawk.h b/ui/squawk.h index c7adddb..5d5d2c6 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -67,6 +67,7 @@ signals: void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); void fileLocalPathRequest(const QString& messageId, const QString& url); + void downloadFileRequest(const QString& messageId, const QString& url); public slots: void newAccount(const QMap& account); @@ -90,6 +91,7 @@ public slots: void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); void fileLocalPathResponse(const QString& messageId, const QString& path); + void downloadFileProgress(const QString& messageId, qreal value); private: typedef std::map Conversations; diff --git a/ui/widgets/message.cpp b/ui/widgets/message.cpp index 5efc94a..70d7e82 100644 --- a/ui/widgets/message.cpp +++ b/ui/widgets/message.cpp @@ -34,6 +34,7 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_ downloadButton(0), file(0), progress(0), + fileComment(0), hasDownloadButton(false), hasProgress(false), hasFile(false) @@ -110,9 +111,15 @@ void Message::addDownloadDialog() hasProgress = false;; } if (!hasDownloadButton) { + if (msg.getBody() == msg.getOutOfBandUrl()) { + text->setText(""); + text->hide(); + } downloadButton = new QPushButton(QIcon::fromTheme("download"), "Download"); + fileComment = new QLabel(sender->text() + " is offering you to download a file"); connect(downloadButton, SIGNAL(clicked()), this, SLOT(onDownload())); - bodyLayout->insertWidget(2, downloadButton); + bodyLayout->insertWidget(2, fileComment); + bodyLayout->insertWidget(3, downloadButton); hasDownloadButton = true; } } @@ -131,10 +138,16 @@ void Message::setProgress(qreal value) } if (hasDownloadButton) { downloadButton->deleteLater(); + fileComment->deleteLater(); downloadButton = 0; + fileComment = 0; hasDownloadButton = false; } if (!hasProgress) { + if (msg.getBody() == msg.getOutOfBandUrl()) { + text->setText(""); + text->hide(); + } progress = new QLabel(std::to_string(value).c_str()); bodyLayout->insertWidget(2, progress); hasProgress = true; @@ -145,16 +158,25 @@ void Message::showFile(const QString& path) { if (hasDownloadButton) { downloadButton->deleteLater(); + fileComment->deleteLater(); downloadButton = 0; + fileComment = 0; hasDownloadButton = false; } if (hasProgress) { progress->deleteLater(); progress = 0; - hasProgress = false;; + hasProgress = false; } if (!hasFile) { - file = new QLabel(path); + if (msg.getBody() == msg.getOutOfBandUrl()) { + text->setText(""); + text->hide(); + } + //file = new QLabel(""); + file = new QLabel(); + file->setPixmap(QPixmap(path)); + //file->setScaledContents(true); bodyLayout->insertWidget(2, file); hasFile = true; } diff --git a/ui/widgets/message.h b/ui/widgets/message.h index 2adb983..e59a372 100644 --- a/ui/widgets/message.h +++ b/ui/widgets/message.h @@ -59,6 +59,7 @@ private: QPushButton* downloadButton; QLabel* file; QLabel* progress; + QLabel* fileComment; bool hasDownloadButton; bool hasProgress; bool hasFile;