diff --git a/CMakeLists.txt b/CMakeLists.txt index f39d0e5..59582a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,15 @@ include_directories(.) find_package(Qt5Widgets CONFIG REQUIRED) find_package(Qt5LinguistTools) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") +message("Build type: ${CMAKE_BUILD_TYPE}") + + set(squawk_SRC main.cpp global.cpp diff --git a/README.md b/README.md index 2f7beca..1473ddc 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,52 @@ A compact XMPP desktop messenger - uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_ - lmdb - CMake 3.0 or higher +- qxmpp 1.1.0 or higher + +### Getting + +The easiest way to get the Squawk is to install it from AUR (if you use Archlinux like distribution) + +Here is the [link](https://aur.archlinux.org/packages/squawk/) for the AUR package + +You can also install it from console if you use some AUR wrapper. Here what it's going to look like with *pacaur* + +``` +$ pacaur -S squawk +``` ### Building +You can also clone the repo and build it from source + Squawk requires Qt with SSL enabled. It uses CMake as build system. -Squawk uses upstream version of QXmpp library so first we need to pull it +There are two ways to build, it depends whether you have qxmpp installed in your system + +#### Building with system qxmpp + +Here is what you do + ``` -git submodule update --init --recursive +$ git clone https://git.macaw.me/blue/squawk +$ cd squawk +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . ``` -Then create a folder for the build, go there and build the project using CMake - + +#### Building with bundled qxmpp + +Here is what you do + ``` -mkdir build -cd build -cmake .. -cmake --build . +$ git clone --recurse-submodules https://git.macaw.me/blue/squawk +$ cd squawk +$ mkdir build +$ cd build +$ cmake .. -D SYSTEM_QXMPP=False +$ cmake --build . ``` ## License diff --git a/core/account.cpp b/core/account.cpp index e3dd29f..492ebdc 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -23,7 +23,7 @@ using namespace Core; -Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent): +Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent): QObject(parent), name(p_name), achiveQueries(), @@ -38,15 +38,20 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& bm(new QXmppBookmarkManager()), rm(client.findExtension()), vm(client.findExtension()), + um(new QXmppUploadRequestManager()), + dm(client.findExtension()), contacts(), conferences(), maxReconnectTimes(0), reconnectTimes(0), queuedContacts(), outOfRosterContacts(), + pendingMessages(), + uploadingSlotsQueue(), avatarHash(), avatarType(), - ownVCardRequestInProgress(false) + ownVCardRequestInProgress(false), + network(p_net) { config.setUser(p_login); config.setDomain(p_server); @@ -85,6 +90,16 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived); //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler + client.addExtension(um); + QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived); + QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed); + + QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived); + QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived); + + QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded); + QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError); + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + name; QDir dir(path); @@ -133,6 +148,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& Account::~Account() { + QObject::disconnect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded); + QObject::disconnect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError); + for (std::map::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) { delete itr->second; } @@ -141,6 +159,7 @@ Account::~Account() delete itr->second; } + delete um; delete bm; delete mm; delete am; @@ -181,6 +200,7 @@ void Core::Account::onClientConnected() reconnectTimes = maxReconnectTimes; state = Shared::connected; cm->setCarbonsEnabled(true); + dm->requestItems(getServer()); emit connectionStateChanged(state); } else { qDebug() << "Something weird had happened - xmpp client reported about successful connection but account wasn't in" << state << "state"; @@ -598,6 +618,7 @@ void Core::Account::sendMessage(const Shared::Message& data) QXmppMessage msg(data.getFrom(), data.getTo(), data.getBody(), data.getThread()); msg.setId(data.getId()); msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible + msg.setOutOfBandUrl(data.getOutOfBandUrl()); RosterItem* ri = 0; std::map::const_iterator itr = contacts.find(data.getPenPalJid()); @@ -623,6 +644,50 @@ void Core::Account::sendMessage(const Shared::Message& data) } } +void Core::Account::sendMessage(const Shared::Message& data, const QString& path) +{ + if (state == Shared::connected) { + QString url = network->getFileRemoteUrl(path); + if (url.size() != 0) { + sendMessageWithLocalUploadedFile(data, url); + } else { + if (network->isUploading(path, data.getId())) { + pendingMessages.emplace(data.getId(), data); + } else { + if (um->serviceFound()) { + QFileInfo file(path); + if (file.exists() && file.isReadable()) { + uploadingSlotsQueue.emplace_back(path, data); + if (uploadingSlotsQueue.size() == 1) { + um->requestUploadSlot(file); + } + } else { + emit onFileUploadError(data.getId(), "Uploading file dissapeared or your system user has no permission to read it"); + qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable"; + } + } else { + emit onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account"); + qDebug() << "Requested upload slot in account" << name << "for file" << path << "but upload manager didn't discover any upload services"; + } + } + } + } else { + emit onFileUploadError(data.getId(), "Account is offline or reconnecting"); + qDebug() << "An attempt to send message with not connected account " << name << ", skipping"; + } +} + +void Core::Account::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url) +{ + msg.setOutOfBandUrl(url); + if (msg.getBody().size() == 0) { + msg.setBody(url); + } + sendMessage(msg); + //TODO removal/progress update +} + + void Core::Account::onCarbonMessageReceived(const QXmppMessage& msg) { handleChatMessage(msg, false, true); @@ -1551,3 +1616,66 @@ void Core::Account::uploadVCard(const Shared::VCard& card) vm->setClientVCard(iq); onOwnVCardReceived(iq); } + +void Core::Account::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) +{ + if (uploadingSlotsQueue.size() == 0) { + qDebug() << "HTTP Upload manager of account" << name << "reports about success requesting upload slot, but none was requested"; + } else { + const std::pair& pair = uploadingSlotsQueue.front(); + const QString& mId = pair.second.getId(); + network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders()); + pendingMessages.emplace(mId, pair.second); + uploadingSlotsQueue.pop_front(); + + if (uploadingSlotsQueue.size() > 0) { + um->requestUploadSlot(uploadingSlotsQueue.front().first); + } + } +} + +void Core::Account::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) +{ + if (uploadingSlotsQueue.size() == 0) { + qDebug() << "HTTP Upload manager of account" << name << "reports about an error requesting upload slot, but none was requested"; + qDebug() << request.error().text(); + } else { + const std::pair& pair = uploadingSlotsQueue.front(); + qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << name << ":" << request.error().text(); + emit uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text()); + + if (uploadingSlotsQueue.size() > 0) { + um->requestUploadSlot(uploadingSlotsQueue.front().first); + } + uploadingSlotsQueue.pop_front(); + } +} + +void Core::Account::onFileUploaded(const QString& messageId, const QString& url) +{ + std::map::const_iterator itr = pendingMessages.find(messageId); + if (itr != pendingMessages.end()) { + sendMessageWithLocalUploadedFile(itr->second, url); + pendingMessages.erase(itr); + } +} + +void Core::Account::onFileUploadError(const QString& messageId, const QString& errMsg) +{ + std::map::const_iterator itr = pendingMessages.find(messageId); + if (itr != pendingMessages.end()) { + pendingMessages.erase(itr); + } +} + +void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items) +{ + for (QXmppDiscoveryIq::Item item : items.items()) { + dm->requestInfo(item.jid()); + } +} + +void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) +{ + +} diff --git a/core/account.h b/core/account.h index a5f07bd..ff77455 100644 --- a/core/account.h +++ b/core/account.h @@ -31,16 +31,20 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include -#include "../global.h" +#include "global.h" #include "contact.h" #include "conference.h" +#include "networkaccess.h" namespace Core { @@ -49,7 +53,7 @@ class Account : public QObject { Q_OBJECT public: - Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent = 0); + Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent = 0); ~Account(); void connect(); @@ -73,6 +77,7 @@ public: void setAvailability(Shared::Availability avail); QString getFullJid() const; void sendMessage(const Shared::Message& data); + void sendMessage(const Shared::Message& data, const QString& path); void requestArchive(const QString& jid, int count, const QString& before); void setReconnectTimes(unsigned int times); void subscribeToContact(const QString& jid, const QString& reason); @@ -112,6 +117,8 @@ signals: void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap& data); void removeRoomParticipant(const QString& jid, const QString& nickName); void receivedVCard(const QString& jid, const Shared::VCard& card); + void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap headers); + void uploadFileError(const QString& messageId, const QString& error); private: QString name; @@ -127,6 +134,8 @@ private: QXmppBookmarkManager* bm; QXmppRosterManager* rm; QXmppVCardManager* vm; + QXmppUploadRequestManager* um; + QXmppDiscoveryManager* dm; std::map contacts; std::map conferences; unsigned int maxReconnectTimes; @@ -135,10 +144,13 @@ private: std::map queuedContacts; std::set outOfRosterContacts; std::set pendingVCardRequests; + std::map pendingMessages; + std::deque> uploadingSlotsQueue; QString avatarHash; QString avatarType; bool ownVCardRequestInProgress; + NetworkAccess* network; private slots: void onClientConnected(); @@ -183,6 +195,13 @@ private slots: void onVCardReceived(const QXmppVCardIq& card); void onOwnVCardReceived(const QXmppVCardIq& card); + + void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot); + void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request); + void onFileUploaded(const QString& messageId, const QString& url); + void onFileUploadError(const QString& messageId, const QString& errMsg); + void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items); + void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info); private: void addedAccount(const QString &bareJid); @@ -199,7 +218,7 @@ private: void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: "); void storeConferences(); void clearConferences(); - + void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url); }; void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card); diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 090f4e7..6e1689d 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -23,7 +23,8 @@ Core::NetworkAccess::NetworkAccess(QObject* parent): running(false), manager(0), files("files"), - downloads() + downloads(), + uploads() { } @@ -34,9 +35,9 @@ Core::NetworkAccess::~NetworkAccess() void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url) { - std::map::iterator itr = downloads.find(url); + std::map::iterator itr = downloads.find(url); if (itr != downloads.end()) { - Download* dwn = itr->second; + Transfer* dwn = itr->second; std::set::const_iterator mItr = dwn->messages.find(messageId); if (mItr == dwn->messages.end()) { dwn->messages.insert(messageId); @@ -63,9 +64,9 @@ void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const Q void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url) { - std::map::iterator itr = downloads.find(url); + std::map::iterator itr = downloads.find(url); if (itr != downloads.end()) { - Download* dwn = itr->second; + Transfer* dwn = itr->second; std::set::const_iterator mItr = dwn->messages.find(messageId); if (mItr == dwn->messages.end()) { dwn->messages.insert(messageId); @@ -85,7 +86,7 @@ void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QSt startDownload(messageId, url); } catch (Archive::Unknown e) { qDebug() << "Error requesting file path:" << e.what(); - startDownload(messageId, url); + emit downloadFileError(messageId, QString("Database error: ") + e.what()); } } } @@ -107,7 +108,7 @@ void Core::NetworkAccess::stop() manager = 0; running = false; - for (std::map::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) { + for (std::map::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) { itr->second->success = false; itr->second->reply->abort(); //assuming it's gonna call onRequestFinished slot } @@ -118,11 +119,11 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT { QNetworkReply* rpl = static_cast(sender()); QString url = rpl->url().toString(); - std::map::const_iterator itr = downloads.find(url); + 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"; + qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; } else { - Download* dwn = itr->second; + Transfer* dwn = itr->second; qreal received = bytesReceived; qreal total = bytesTotal; qreal progress = received/total; @@ -133,132 +134,18 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT } } -void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code) +void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) { QNetworkReply* rpl = static_cast(sender()); QString url = rpl->url().toString(); - std::map::const_iterator itr = downloads.find(url); + 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"; + qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping"; } else { - QString errorText; - switch (code) { - case QNetworkReply::NoError: - //this never is supposed to happen - break; - - // network layer errors [relating to the destination server] (1-99): - case QNetworkReply::ConnectionRefusedError: - errorText = "Connection refused"; - break; - case QNetworkReply::RemoteHostClosedError: - errorText = "Remote server closed the connection"; - break; - case QNetworkReply::HostNotFoundError: - errorText = "Remote host is not found"; - break; - case QNetworkReply::TimeoutError: - errorText = "Connection was closed because it timed out"; - break; - case QNetworkReply::OperationCanceledError: - //this means I closed it myself by abort() or close(), don't think I need to notify here - break; - case QNetworkReply::SslHandshakeFailedError: - errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here - break; - case QNetworkReply::TemporaryNetworkFailureError: - //this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify - break; - case QNetworkReply::NetworkSessionFailedError: - errorText = "Outgoing connection problem"; - break; - case QNetworkReply::BackgroundRequestNotAllowedError: - errorText = "Background request is not allowed"; - break; - case QNetworkReply::TooManyRedirectsError: - errorText = "The request was redirected too many times"; - break; - case QNetworkReply::InsecureRedirectError: - errorText = "The request was redirected to insecure connection"; - break; - case QNetworkReply::UnknownNetworkError: - errorText = "Unknown network error"; - break; - - // proxy errors (101-199): - case QNetworkReply::ProxyConnectionRefusedError: - errorText = "The connection to the proxy server was refused"; - break; - case QNetworkReply::ProxyConnectionClosedError: - errorText = "Proxy server closed the connection"; - break; - case QNetworkReply::ProxyNotFoundError: - errorText = "Proxy host was not found"; - break; - case QNetworkReply::ProxyTimeoutError: - errorText = "Connection to the proxy server was closed because it timed out"; - break; - case QNetworkReply::ProxyAuthenticationRequiredError: - errorText = "Couldn't connect to proxy server, authentication is required"; - break; - case QNetworkReply::UnknownProxyError: - errorText = "Unknown proxy error"; - break; - - // content errors (201-299): - case QNetworkReply::ContentAccessDenied: - errorText = "The access to file is denied"; - break; - case QNetworkReply::ContentOperationNotPermittedError: - errorText = "The operation over requesting file is not permitted"; - break; - case QNetworkReply::ContentNotFoundError: - errorText = "The file was not found"; - break; - case QNetworkReply::AuthenticationRequiredError: - errorText = "Couldn't access the file, authentication is required"; - break; - case QNetworkReply::ContentReSendError: - errorText = "Sending error, one more attempt will probably solve this problem"; - break; - case QNetworkReply::ContentConflictError: - errorText = "The request could not be completed due to a conflict with the current state of the resource"; - break; - case QNetworkReply::ContentGoneError: - errorText = "The requested resource is no longer available at the server"; - break; - case QNetworkReply::UnknownContentError: - errorText = "Unknown content error"; - break; - - // protocol errors - case QNetworkReply::ProtocolUnknownError: - errorText = "Unknown protocol error"; - break; - case QNetworkReply::ProtocolInvalidOperationError: - errorText = "Requested operation is not permitted in this protocol"; - break; - case QNetworkReply::ProtocolFailure: - errorText = "Low level protocol error"; - break; - - // Server side errors (401-499) - case QNetworkReply::InternalServerError: - errorText = "Internal server error"; - break; - case QNetworkReply::OperationNotImplementedError: - errorText = "Server doesn't support requested operation"; - break; - case QNetworkReply::ServiceUnavailableError: - errorText = "The server is not available for this operation right now"; - break; - case QNetworkReply::UnknownServerError: - errorText = "Unknown server error"; - break; - } + QString errorText = getErrorText(code); if (errorText.size() > 0) { itr->second->success = false; - Download* dwn = itr->second; + Transfer* dwn = itr->second; for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { emit downloadFileError(*mItr, errorText); } @@ -266,16 +153,137 @@ void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code) } } -void Core::NetworkAccess::onRequestFinished() +QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) +{ + QString errorText(""); + switch (code) { + case QNetworkReply::NoError: + //this never is supposed to happen + break; + + // network layer errors [relating to the destination server] (1-99): + case QNetworkReply::ConnectionRefusedError: + errorText = "Connection refused"; + break; + case QNetworkReply::RemoteHostClosedError: + errorText = "Remote server closed the connection"; + break; + case QNetworkReply::HostNotFoundError: + errorText = "Remote host is not found"; + break; + case QNetworkReply::TimeoutError: + errorText = "Connection was closed because it timed out"; + break; + case QNetworkReply::OperationCanceledError: + //this means I closed it myself by abort() or close(), don't think I need to notify here + break; + case QNetworkReply::SslHandshakeFailedError: + errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here + break; + case QNetworkReply::TemporaryNetworkFailureError: + //this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify + break; + case QNetworkReply::NetworkSessionFailedError: + errorText = "Outgoing connection problem"; + break; + case QNetworkReply::BackgroundRequestNotAllowedError: + errorText = "Background request is not allowed"; + break; + case QNetworkReply::TooManyRedirectsError: + errorText = "The request was redirected too many times"; + break; + case QNetworkReply::InsecureRedirectError: + errorText = "The request was redirected to insecure connection"; + break; + case QNetworkReply::UnknownNetworkError: + errorText = "Unknown network error"; + break; + + // proxy errors (101-199): + case QNetworkReply::ProxyConnectionRefusedError: + errorText = "The connection to the proxy server was refused"; + break; + case QNetworkReply::ProxyConnectionClosedError: + errorText = "Proxy server closed the connection"; + break; + case QNetworkReply::ProxyNotFoundError: + errorText = "Proxy host was not found"; + break; + case QNetworkReply::ProxyTimeoutError: + errorText = "Connection to the proxy server was closed because it timed out"; + break; + case QNetworkReply::ProxyAuthenticationRequiredError: + errorText = "Couldn't connect to proxy server, authentication is required"; + break; + case QNetworkReply::UnknownProxyError: + errorText = "Unknown proxy error"; + break; + + // content errors (201-299): + case QNetworkReply::ContentAccessDenied: + errorText = "The access to file is denied"; + break; + case QNetworkReply::ContentOperationNotPermittedError: + errorText = "The operation over requesting file is not permitted"; + break; + case QNetworkReply::ContentNotFoundError: + errorText = "The file was not found"; + break; + case QNetworkReply::AuthenticationRequiredError: + errorText = "Couldn't access the file, authentication is required"; + break; + case QNetworkReply::ContentReSendError: + errorText = "Sending error, one more attempt will probably solve this problem"; + break; + case QNetworkReply::ContentConflictError: + errorText = "The request could not be completed due to a conflict with the current state of the resource"; + break; + case QNetworkReply::ContentGoneError: + errorText = "The requested resource is no longer available at the server"; + break; + case QNetworkReply::UnknownContentError: + errorText = "Unknown content error"; + break; + + // protocol errors + case QNetworkReply::ProtocolUnknownError: + errorText = "Unknown protocol error"; + break; + case QNetworkReply::ProtocolInvalidOperationError: + errorText = "Requested operation is not permitted in this protocol"; + break; + case QNetworkReply::ProtocolFailure: + errorText = "Low level protocol error"; + break; + + // Server side errors (401-499) + case QNetworkReply::InternalServerError: + errorText = "Internal server error"; + break; + case QNetworkReply::OperationNotImplementedError: + errorText = "Server doesn't support requested operation"; + break; + case QNetworkReply::ServiceUnavailableError: + errorText = "The server is not available for this operation right now"; + break; + case QNetworkReply::UnknownServerError: + errorText = "Unknown server error"; + break; + } + return errorText; +} + + +void Core::NetworkAccess::onDownloadFinished() { QString path(""); QNetworkReply* rpl = static_cast(sender()); QString url = rpl->url().toString(); - std::map::const_iterator itr = downloads.find(url); + 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; + Transfer* dwn = itr->second; if (dwn->success) { qDebug() << "download success for" << url; QStringList hops = url.split("/"); @@ -320,13 +328,173 @@ void Core::NetworkAccess::onRequestFinished() void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url) { - Download* dwn = new Download({{messageId}, 0, 0, true}); + Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0}); QNetworkRequest req(url); dwn->reply = manager->get(req); connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress); - connect(dwn->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onRequestError); - connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onRequestFinished); + connect(dwn->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onDownloadError); + connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished); downloads.insert(std::make_pair(url, dwn)); emit downloadFileProgress(messageId, 0); } +void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) +{ + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = uploads.find(url); + if (itr == uploads.end()) { + qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping"; + } else { + QString errorText = getErrorText(code); + if (errorText.size() > 0) { + itr->second->success = false; + Transfer* upl = itr->second; + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit uploadFileError(*mItr, errorText); + } + } + } +} + +void Core::NetworkAccess::onUploadFinished() +{ + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = uploads.find(url); + if (itr == downloads.end()) { + qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping"; + } else { + Transfer* upl = itr->second; + if (upl->success) { + qDebug() << "upload success for" << url; + files.addRecord(upl->url, upl->path); + + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit fileLocalPathResponse(*mItr, upl->path); + emit uploadFileComplete(*mItr, upl->url); + } + } + + upl->reply->deleteLater(); + upl->file->close(); + upl->file->deleteLater(); + delete upl; + uploads.erase(itr); + } +} + +void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = uploads.find(url); + if (itr == uploads.end()) { + qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; + } else { + Transfer* upl = itr->second; + qreal received = bytesReceived; + qreal total = bytesTotal; + qreal progress = received/total; + upl->progress = progress; + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit uploadFileProgress(*mItr, progress); + } + } +} + +void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path) +{ + Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0}); + QNetworkRequest req(url); + QFile* file = new QFile(path); + if (file->open(QIODevice::ReadOnly)) { + upl->reply = manager->put(req, file); + + connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress); + connect(upl->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onUploadError); + connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished); + uploads.insert(std::make_pair(url, upl)); + emit downloadFileProgress(messageId, 0); + } else { + qDebug() << "couldn't upload file" << path; + emit uploadFileError(messageId, "Error opening file"); + delete file; + } +} + +void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path) +{ + std::map::iterator itr = uploads.find(url); + if (itr != uploads.end()) { + Transfer* upl = itr->second; + std::set::const_iterator mItr = upl->messages.find(messageId); + if (mItr == upl->messages.end()) { + upl->messages.insert(messageId); + } + emit uploadFileProgress(messageId, upl->progress); + } else { + try { + QString ePath = files.getRecord(url); + if (ePath == path) { + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startUpload(messageId, url, path); + } + } else { + QFileInfo info(path); + if (info.exists() && info.isFile()) { + files.changeRecord(url, path); + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startUpload(messageId, url, path); + } + } + } catch (Archive::NotFound e) { + startUpload(messageId, url, path); + } catch (Archive::Unknown e) { + qDebug() << "Error requesting file path on upload:" << e.what(); + emit uploadFileError(messageId, QString("Database error: ") + e.what()); + } + } +} + +QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) +{ + return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url +} + +bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId) +{ + return false; //TODO this is a way to avoid parallel uploading of the same files by different chats + // message is is supposed to be added to the uploading messageids list + // the result should be true if there was an uploading file with this path + // message id can be empty, then it's just to check and not to add +} + +void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap headers) +{ + QFile* file = new QFile(path); + Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file}); + QNetworkRequest req(put); + for (QMap::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) { + req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8()); + } + if (file->open(QIODevice::ReadOnly)) { + upl->reply = manager->put(req, file); + + connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress); + connect(upl->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onUploadError); + connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished); + uploads.insert(std::make_pair(put.toString(), upl)); + emit downloadFileProgress(messageId, 0); + } else { + qDebug() << "couldn't upload file" << path; + emit uploadFileError(messageId, "Error opening file"); + delete file; + } +} diff --git a/core/networkaccess.h b/core/networkaccess.h index 526cf2d..824b1af 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -39,7 +39,7 @@ namespace Core { class NetworkAccess : public QObject { Q_OBJECT - struct Download; + struct Transfer; public: NetworkAccess(QObject* parent = nullptr); virtual ~NetworkAccess(); @@ -47,34 +47,51 @@ public: void start(); void stop(); + QString getFileRemoteUrl(const QString& path); + bool isUploading(const QString& path, const QString& messageId = ""); + void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap headers); + signals: void fileLocalPathResponse(const QString& messageId, const QString& path); void downloadFileProgress(const QString& messageId, qreal value); void downloadFileError(const QString& messageId, const QString& path); + void uploadFileProgress(const QString& messageId, qreal value); + void uploadFileError(const QString& messageId, const QString& path); + void uploadFileComplete(const QString& messageId, const QString& url); public slots: void fileLocalPathRequest(const QString& messageId, const QString& url); void downladFileRequest(const QString& messageId, const QString& url); + void uploadFileRequest(const QString& messageId, const QString& url, const QString& path); private: void startDownload(const QString& messageId, const QString& url); + void startUpload(const QString& messageId, const QString& url, const QString& path); + QString getErrorText(QNetworkReply::NetworkError code); private slots: void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void onRequestError(QNetworkReply::NetworkError code); - void onRequestFinished(); + void onDownloadError(QNetworkReply::NetworkError code); + void onDownloadFinished(); + void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onUploadError(QNetworkReply::NetworkError code); + void onUploadFinished(); private: bool running; QNetworkAccessManager* manager; Storage files; - std::map downloads; + std::map downloads; + std::map uploads; - struct Download { + struct Transfer { std::set messages; qreal progress; QNetworkReply* reply; bool success; + QString path; + QString url; + QFile* file; }; }; diff --git a/core/squawk.cpp b/core/squawk.cpp index 9f421c9..7ca41e3 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -31,6 +31,8 @@ Core::Squawk::Squawk(QObject* parent): connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); + connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); + connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError); } Core::Squawk::~Squawk() @@ -104,7 +106,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const QSettings settings; unsigned int reconnects = settings.value("reconnects", 2).toUInt(); - Account* acc = new Account(login, server, password, name); + Account* acc = new Account(login, server, password, name, &network); acc->setResource(resource); acc->setReconnectTimes(reconnects); accounts.push_back(acc); @@ -135,6 +137,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard); + connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError); + QMap map = { {"login", login}, {"server", server}, @@ -281,6 +285,17 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da itr->second->sendMessage(data); } +void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug("An attempt to send a message with non existing account, skipping"); + return; + } + + itr->second->sendMessage(data, path); +} + void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) { AccountsMap::const_iterator itr = amap.find(account); @@ -373,7 +388,6 @@ void Core::Squawk::removeAccountRequest(const QString& name) acc->deleteLater(); } - void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason) { AccountsMap::const_iterator itr = amap.find(account); diff --git a/core/squawk.h b/core/squawk.h index 88ea860..e2a6b74 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -66,6 +66,8 @@ signals: void fileLocalPathResponse(const QString& messageId, const QString& path); void downloadFileError(const QString& messageId, const QString& error); void downloadFileProgress(const QString& messageId, qreal value); + void uploadFileError(const QString& messageId, const QString& error); + void uploadFileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); public slots: @@ -78,6 +80,7 @@ public slots: void disconnectAccount(const QString& account); void changeState(int state); void sendMessage(const QString& account, const Shared::Message& data); + void sendMessage(const QString& account, const Shared::Message& data, const QString& path); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); diff --git a/core/storage.cpp b/core/storage.cpp index 8f6c17c..7ff0ef7 100644 --- a/core/storage.cpp +++ b/core/storage.cpp @@ -73,7 +73,7 @@ void Core::Storage::close() void Core::Storage::addRecord(const QString& key, const QString& value) { if (!opened) { - throw Archive::Closed("addElement", name.toStdString()); + throw Archive::Closed("addRecord", name.toStdString()); } const std::string& id = key.toStdString(); const std::string& val = value.toStdString(); @@ -99,6 +99,33 @@ void Core::Storage::addRecord(const QString& key, const QString& value) } } +void Core::Storage::changeRecord(const QString& key, const QString& value) +{ + if (!opened) { + throw Archive::Closed("changeRecord", name.toStdString()); + } + const std::string& id = key.toStdString(); + const std::string& val = value.toStdString(); + + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = id.size(); + lmdbKey.mv_data = (char*)id.c_str(); + lmdbData.mv_size = val.size(); + lmdbData.mv_data = (char*)val.c_str(); + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc) { + throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + QString Core::Storage::getRecord(const QString& key) const { if (!opened) { diff --git a/core/storage.h b/core/storage.h index 9fe64ca..d2abfde 100644 --- a/core/storage.h +++ b/core/storage.h @@ -39,6 +39,7 @@ public: void close(); void addRecord(const QString& key, const QString& value); + void changeRecord(const QString& key, const QString& value); void removeRecord(const QString& key); QString getRecord(const QString& key) const; diff --git a/external/qxmpp b/external/qxmpp index b18a57d..f8c546c 160000 --- a/external/qxmpp +++ b/external/qxmpp @@ -1 +1 @@ -Subproject commit b18a57daa33f0fefa5f4c63aa7f448b48d302e0d +Subproject commit f8c546c5b701c53d708a38a951fcc734eaee7940 diff --git a/main.cpp b/main.cpp index 1c455bc..ce23acc 100644 --- a/main.cpp +++ b/main.cpp @@ -39,7 +39,7 @@ int main(int argc, char *argv[]) QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.0.5"); + QApplication::setApplicationVersion("0.1.1"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); @@ -90,7 +90,10 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); - QObject::connect(&w, &Squawk::sendMessage, squawk, &Core::Squawk::sendMessage); + QObject::connect(&w, qOverload(&Squawk::sendMessage), + squawk, qOverload(&Core::Squawk::sendMessage)); + QObject::connect(&w, qOverload(&Squawk::sendMessage), + squawk, qOverload(&Core::Squawk::sendMessage)); QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); @@ -131,13 +134,12 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse); - QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress); - QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError); + QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress); + QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError); + QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); + QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); - - //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); - coreThread->start(); int result = app.exec(); diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index bd979b5..aec6d05 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,18 +1,18 @@ # Maintainer: Yury Gubich pkgname=squawk -pkgver=0.0.5 +pkgver=0.1.1 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on qt" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/squawk" license=('GPL3') -depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.0.0') +depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0') makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') source=("$pkgname-$pkgver.tar.gz") -sha256sums=('12bfc517574387257a82143d8970ec0d8d434ccd32f7ac400355ed5fa18192ab') +sha256sums=('d0448f2fdb321e31a40c08b77adc951bafe8d1c271f70d6ffc80fb17cef670cf') build() { cd "$srcdir/squawk" - cmake . -D CMAKE_INSTALL_PREFIX=/usr + cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release cmake --build . -j $nproc } package() { diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts index e7bae69..412b6d8 100644 --- a/translations/squawk.ru.ts +++ b/translations/squawk.ru.ts @@ -5,77 +5,92 @@ Account + Account Заголовок окна Учетная запись + Your account login Имя пользователя Вашей учетной записи + john_smith1987 ivan_ivanov1987 + Server Сервер + A server address of your account. Like 404.city or macaw.me Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) + macaw.me macaw.me + Login Имя учетной записи + Password Пароль + Password of your account Пароль вашей учетной записи + Name Имя + Just a name how would you call this account, doesn't affect anything Просто имя, то как Вы называете свою учетную запись, может быть любым + John Иван + Resource Ресурс + A resource name like "Home" or "Work" Имя этой программы для ваших контактов, может быть "Home" или "Phone" + QXmpp Ресурс по умолчанию QXmpp @@ -85,31 +100,37 @@ Accounts + Accounts Учетные записи + Delete Удалить + Add Добавить + Edit Редактировать + Change password Изменить пароль + Connect @@ -125,11 +146,12 @@ Conversation + Type your message here... Введите сообщение... - + Chose a file to send Выберите файл для отправки @@ -281,52 +303,62 @@ JoinConference + Join new conference Заголовок окна Присоединиться к новой беседе + JID JID + Room JID Jabber-идентификатор беседы + identifier@conference.server.org identifier@conference.server.org + Account Учетная запись + Join on login Автовход + If checked Squawk will try to join this conference on login Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении + Nick name Псевдоним + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи + John Ivan @@ -334,26 +366,58 @@ Message - Download Скачать - + + Open + Открыть + + + + MessageLine + + + Downloading... + Скачивается... + + + + + Download + Скачать + + + + Push the button to daownload the file + Нажмите на кнопку что бы загрузить файл + + + + Error uploading file: %1 +You can try again + Ошибка загрузки файла на сервер: +%1 +Для того, что бы попробовать снова нажмите на кнопку + + + + Upload + Загрузить + + + Error downloading file: %1 You can try again - Ошибка загрузки файла: %1 + Ошибка скачивания файла: +%1 Вы можете попробовать снова - - %1 is offering you to download a file - %1 предлагает Вам скачать файл - - - - Open - Открыть + + Uploading... + Загружается... @@ -470,48 +534,57 @@ You can try again NewContact + Add new contact Заголовок окна Добавление нового контакта + Account Учетная запись + An account that is going to have new contact Учетная запись для которой будет добавлен контакт + JID JID + Jabber id of your new contact Jabber-идентификатор нового контакта + name@server.dmn Placeholder поля ввода JID name@server.dmn + Name Имя + The way this new contact will be labeled in your roster (optional) То, как будет подписан контакт в вашем списке контактов (не обязательно) + John Smith Иван Иванов @@ -520,36 +593,43 @@ You can try again Squawk + squawk Squawk + Settings Настройки + Squawk Squawk + Accounts Учетные записи + Quit Выйти + Add contact Добавить контакт + Add conference Присоединиться к беседе @@ -559,52 +639,52 @@ You can try again Список контактов - + Disconnect Отключить - + Connect Подключить - - + + VCard Карточка - - - + + + Remove Удалить - + Open dialog Открыть диалог - - + + Unsubscribe Отписаться - - + + Subscribe Подписаться - + Rename Переименовать - + Input new name for %1 or leave it empty for the contact to be displayed as %1 @@ -614,47 +694,47 @@ to be displayed as %1 %1 - + Renaming %1 Назначение имени контакту %1 - + Groups Группы - + New group Создать новую группу - + New group name Имя группы - + Add %1 to a new group Добавление %1 в новую группу - + Open conversation Открыть окно беседы - + %1 account card Карточка учетной записи %1 - + %1 contact card Карточка контакта %1 - + Downloading vCard Получение карточки @@ -663,119 +743,145 @@ to be displayed as %1 VCard + Received 12.07.2007 at 17.35 Не обновлялось + + General Общее + Organization Место работы + Middle name Среднее имя + First name Имя + Last name Фамилия + Nick name Псевдоним + Birthday Дата рождения + Organization name Название организации + Unit / Department Отдел + Role / Profession Профессия + Job title Наименование должности + Full name Полное имя + Personal information Личная информация + Addresses Адреса + E-Mail addresses Адреса электронной почты + Phone numbers Номера телефонов + + Contact Контактная информация + Jabber ID Jabber ID + Web site Веб сайт + + Description Описание + Set avatar Установить иконку + Clear avatar Убрать иконку diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 396eeb6..095f639 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -300,7 +300,9 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) conv->setAttribute(Qt::WA_DeleteOnClose); connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); + connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); + connect(conv, qOverload(&Conversation::sendMessage), + this, qOverload(&Squawk::onConversationMessage)); connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); @@ -367,11 +369,11 @@ void Squawk::onConversationDownloadFile(const QString& messageId, const QString& } } -void Squawk::downloadFileProgress(const QString& messageId, qreal value) +void Squawk::fileProgress(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"; + qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; return; } else { const std::set& convs = itr->second; @@ -379,17 +381,17 @@ void Squawk::downloadFileProgress(const QString& messageId, qreal value) const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); if (c != conversations.end()) { - c->second->responseDownloadProgress(messageId, value); + c->second->responseFileProgress(messageId, value); } } } } -void Squawk::downloadFileError(const QString& messageId, const QString& error) +void Squawk::fileError(const QString& messageId, const QString& error) { std::map>::const_iterator itr = requestedFiles.find(messageId); if (itr == requestedFiles.end()) { - qDebug() << "downloadFileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; + qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; return; } else { const std::set& convs = itr->second; @@ -397,7 +399,7 @@ void Squawk::downloadFileError(const QString& messageId, const QString& error) const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); if (c != conversations.end()) { - c->second->downloadError(messageId, error); + c->second->fileError(messageId, error); } } requestedFiles.erase(itr); @@ -489,10 +491,18 @@ void Squawk::notify(const QString& account, const Shared::Message& msg) void Squawk::onConversationMessage(const Shared::Message& msg) { Conversation* conv = static_cast(sender()); - emit sendMessage(conv->getAccount(), msg); } +void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path) +{ + Conversation* conv = static_cast(sender()); + std::map>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set())).first; + itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); + + emit sendMessage(conv->getAccount(), msg, path); +} + void Squawk::onConversationRequestArchive(const QString& before) { Conversation* conv = static_cast(sender()); @@ -518,7 +528,6 @@ void Squawk::removeAccount(const QString& account) ++itr; Conversation* conv = lItr->second; disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - disconnect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conv->close(); diff --git a/ui/squawk.h b/ui/squawk.h index 819d255..b464fd2 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -58,6 +58,7 @@ signals: void disconnectAccount(const QString&); void changeState(int state); void sendMessage(const QString& account, const Shared::Message& data); + void sendMessage(const QString& account, const Shared::Message& data, const QString& path); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); @@ -97,8 +98,8 @@ 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 downloadFileError(const QString& messageId, const QString& error); - void downloadFileProgress(const QString& messageId, qreal value); + void fileError(const QString& messageId, const QString& error); + void fileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); private: @@ -132,6 +133,7 @@ private slots: void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); void onConversationMessage(const Shared::Message& msg); + void onConversationMessage(const Shared::Message& msg, const QString& path); void onConversationRequestArchive(const QString& before); void onRosterContextMenu(const QPoint& point); void onConversationShown(); diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index 951037a..eb4b608 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -34,16 +34,14 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_ sender(new QLabel(p_sender)), text(new QLabel()), shadow(new QGraphicsDropShadowEffect()), - downloadButton(0), + button(0), file(0), progress(0), fileComment(new QLabel()), - errorText(""), - hasDownloadButton(false), + hasButton(false), hasProgress(false), hasFile(false), - commentAdded(false), - errorDownloadingFile(false) + commentAdded(false) { body->setBackgroundRole(QPalette::AlternateBase); body->setAutoFillBackground(true); @@ -79,13 +77,13 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_ body->setGraphicsEffect(shadow); if (outgoing) { - addWidget(body); - addStretch(); - } else { sender->setAlignment(Qt::AlignRight); date->setAlignment(Qt::AlignRight); addStretch(); addWidget(body); + } else { + addWidget(body); + addStretch(); } } @@ -94,6 +92,7 @@ Message::~Message() if (!commentAdded) { delete fileComment; } + delete body; } QString Message::getId() const @@ -101,47 +100,38 @@ QString Message::getId() const return msg.getId(); } +QString Message::getFileUrl() const +{ + return msg.getOutOfBandUrl(); +} + void Message::setSender(const QString& p_sender) { sender->setText(p_sender); } -void Message::addDownloadDialog() +void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip) { hideFile(); hideProgress(); - if (!hasDownloadButton) { + if (!hasButton) { hideComment(); if (msg.getBody() == msg.getOutOfBandUrl()) { text->setText(""); text->hide(); } - downloadButton = new QPushButton(QIcon::fromTheme("download"), tr("Download")); - downloadButton->setToolTip("" + msg.getOutOfBandUrl() + ""); - if (errorDownloadingFile) { - fileComment->setWordWrap(true); - fileComment->setText(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", errorText.toLatin1()))); - } else { - fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text())); - } - fileComment->show(); - connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload); - bodyLayout->insertWidget(2, fileComment); - bodyLayout->insertWidget(3, downloadButton); - hasDownloadButton = true; - commentAdded = true; + button = new QPushButton(icon, buttonText); + button->setToolTip(tooltip); + connect(button, &QPushButton::clicked, this, &Message::buttonClicked); + bodyLayout->insertWidget(2, button); + hasButton = true; } } -void Message::onDownload() -{ - emit downloadFile(msg.getId(), msg.getOutOfBandUrl()); -} - void Message::setProgress(qreal value) { hideFile(); - hideDownload(); + hideButton(); if (!hasProgress) { hideComment(); if (msg.getBody() == msg.getOutOfBandUrl()) { @@ -150,19 +140,15 @@ void Message::setProgress(qreal value) } progress = new QProgressBar(); progress->setRange(0, 100); - fileComment->setText("Downloading..."); - fileComment->show(); bodyLayout->insertWidget(2, progress); - bodyLayout->insertWidget(3, fileComment); hasProgress = true; - commentAdded = true; } progress->setValue(value * 100); } void Message::showFile(const QString& path) { - hideDownload(); + hideButton(); hideProgress(); if (!hasFile) { hideComment(); @@ -175,16 +161,13 @@ void Message::showFile(const QString& path) QStringList parts = type.name().split("/"); QString big = parts.front(); QFileInfo info(path); - fileComment = new QLabel(); if (big == "image") { file = new Image(path); } else { file = new QLabel(); file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50)); file->setAlignment(Qt::AlignCenter); - fileComment->setText(info.fileName()); - fileComment->setWordWrap(true); - fileComment->show(); + showComment(info.fileName(), true); } file->setContextMenuPolicy(Qt::ActionsContextMenu); QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file); @@ -193,9 +176,7 @@ void Message::showFile(const QString& path) }); file->addAction(openAction); bodyLayout->insertWidget(2, file); - bodyLayout->insertWidget(3, fileComment); hasFile = true; - commentAdded = true; } } @@ -208,13 +189,12 @@ void Message::hideComment() } } -void Message::hideDownload() +void Message::hideButton() { - if (hasDownloadButton) { - downloadButton->deleteLater(); - downloadButton = 0; - hasDownloadButton = false; - errorDownloadingFile = false; + if (hasButton) { + button->deleteLater(); + button = 0; + hasButton = false; } } @@ -235,10 +215,29 @@ void Message::hideProgress() hasProgress = false;; } } - -void Message::showError(const QString& error) +void Message::showComment(const QString& comment, bool wordWrap) { - errorDownloadingFile = true; - errorText = error; - addDownloadDialog(); + if (!commentAdded) { + int index = 2; + if (hasFile) { + index++; + } + if (hasButton) { + index++; + } + if (hasProgress) { + index++; + } + bodyLayout->insertWidget(index, fileComment); + fileComment->show(); + commentAdded = true; + } + fileComment->setWordWrap(wordWrap); + fileComment->setText(comment); } + +const Shared::Message & Message::getMessage() const +{ + return msg; +} + diff --git a/ui/utils/message.h b/ui/utils/message.h index 8a89268..6bea433 100644 --- a/ui/utils/message.h +++ b/ui/utils/message.h @@ -30,9 +30,9 @@ #include #include -#include "../../global.h" -#include "../utils/resizer.h" -#include "../utils/image.h" +#include "global.h" +#include "resizer.h" +#include "image.h" /** * @todo write docs @@ -46,14 +46,17 @@ public: void setSender(const QString& sender); QString getId() const; + QString getFileUrl() const; + const Shared::Message& getMessage() const; - void addDownloadDialog(); + void addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip = ""); + void showComment(const QString& comment, bool wordWrap = false); + void hideComment(); void showFile(const QString& path); - void showError(const QString& error); void setProgress(qreal value); signals: - void downloadFile(const QString& messageId, const QString& url); + void buttonClicked(); private: Shared::Message msg; @@ -63,25 +66,19 @@ private: QLabel* sender; QLabel* text; QGraphicsDropShadowEffect* shadow; - QPushButton* downloadButton; + QPushButton* button; QLabel* file; QProgressBar* progress; QLabel* fileComment; - QString errorText; - bool hasDownloadButton; + bool hasButton; bool hasProgress; bool hasFile; bool commentAdded; - bool errorDownloadingFile; - -private slots: - void onDownload(); private: - void hideDownload(); + void hideButton(); void hideProgress(); void hideFile(); - void hideComment(); }; #endif // MESSAGE_H diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index 06efa85..befef70 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -26,10 +26,12 @@ MessageLine::MessageLine(bool p_room, QWidget* parent): messageOrder(), myMessages(), palMessages(), + uploadPaths(), layout(new QVBoxLayout(this)), myName(), palNames(), - views(), + uploading(), + downloading(), room(p_room), busyShown(false), progress() @@ -45,7 +47,7 @@ MessageLine::~MessageLine() } } -MessageLine::Position MessageLine::message(const Shared::Message& msg) +MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forceOutgoing) { QString id = msg.getId(); Index::iterator itr = messageIndex.find(id); @@ -57,27 +59,32 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) QString sender; bool outgoing; - if (room) { - if (msg.getFromResource() == myName) { - sender = myName; - outgoing = false; - } else { - sender = msg.getFromResource(); - outgoing = true; - } + if (forceOutgoing) { + sender = myName; + outgoing = true; } else { - if (msg.getOutgoing()) { - sender = myName; - outgoing = false; - } else { - QString jid = msg.getFromJid(); - std::map::iterator itr = palNames.find(jid); - if (itr != palNames.end()) { - sender = itr->second; + if (room) { + if (msg.getFromResource() == myName) { + sender = myName; + outgoing = true; } else { - sender = jid; + sender = msg.getFromResource(); + outgoing = false; + } + } else { + if (msg.getOutgoing()) { + sender = myName; + outgoing = true; + } else { + QString jid = msg.getFromJid(); + std::map::iterator itr = palNames.find(jid); + if (itr != palNames.end()) { + sender = itr->second; + } else { + sender = jid; + } + outgoing = false; } - outgoing = true; } } @@ -90,6 +97,8 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) return invalid; } if (outgoing) { + myMessages.insert(std::make_pair(id, message)); + } else { if (room) { } else { @@ -100,8 +109,6 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) } pItr->second.insert(std::make_pair(id, message)); } - } else { - myMessages.insert(std::make_pair(id, message)); } messageIndex.insert(std::make_pair(id, message)); int index = std::distance(messageOrder.begin(), result.first); //need to make with binary indexed tree @@ -125,14 +132,29 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) layout->insertLayout(index, message); } - if (msg.hasOutOfBandUrl()) {\ + if (msg.hasOutOfBandUrl()) { emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl()); - connect(message, &Message::downloadFile, this, &MessageLine::downloadFile); + connect(message, &Message::buttonClicked, this, &MessageLine::onDownload); } return res; } +void MessageLine::onDownload() +{ + Message* msg = static_cast(sender()); + QString messageId = msg->getId(); + Index::const_iterator itr = downloading.find(messageId); + if (itr == downloading.end()) { + downloading.insert(std::make_pair(messageId, msg)); + msg->setProgress(0); + msg->showComment(tr("Downloading...")); + emit downloadFile(messageId, msg->getFileUrl()); + } else { + qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping"; + } +} + void MessageLine::setMyName(const QString& name) { myName = name; @@ -192,11 +214,11 @@ void MessageLine::hideBusyIndicator() } } -void MessageLine::responseDownloadProgress(const QString& messageId, qreal progress) +void MessageLine::fileProgress(const QString& messageId, qreal progress) { Index::const_iterator itr = messageIndex.find(messageId); if (itr == messageIndex.end()) { - + //TODO may be some logging, that's not normal } else { itr->second->setProgress(progress); } @@ -208,21 +230,121 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat if (itr == messageIndex.end()) { } else { + Index::const_iterator uItr = uploading.find(messageId); if (path.size() > 0) { - itr->second->showFile(path); + Index::const_iterator dItr = downloading.find(messageId); + if (dItr != downloading.end()) { + downloading.erase(dItr); + itr->second->showFile(path); + } else { + if (uItr != uploading.end()) { + uploading.erase(uItr); + std::map::const_iterator muItr = uploadPaths.find(messageId); + if (muItr != uploadPaths.end()) { + uploadPaths.erase(muItr); + } + if (room) { + removeMessage(messageId); + } else { + Shared::Message msg = itr->second->getMessage(); + removeMessage(messageId); + msg.setCurrentTime(); + message(msg); + itr = messageIndex.find(messageId); + itr->second->showFile(path); + } + } else { + itr->second->showFile(path); //then it is already cached file + } + } } else { - itr->second->addDownloadDialog(); + if (uItr == uploading.end()) { + const Shared::Message& msg = itr->second->getMessage(); + itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "" + msg.getOutOfBandUrl() + ""); + itr->second->showComment(tr("Push the button to daownload the file")); + } else { + qDebug() << "An unhandled state for file uploading - empty path"; + } } } } -void MessageLine::downloadError(const QString& messageId, const QString& error) +void MessageLine::removeMessage(const QString& messageId) { Index::const_iterator itr = messageIndex.find(messageId); - if (itr == messageIndex.end()) { - + if (itr != messageIndex.end()) { + Message* ui = itr->second; + const Shared::Message& msg = ui->getMessage(); + messageIndex.erase(itr); + Order::const_iterator oItr = messageOrder.find(msg.getTime()); + if (oItr != messageOrder.end()) { + messageOrder.erase(oItr); + } else { + qDebug() << "An attempt to remove message from messageLine, but it wasn't found in order"; + } + if (msg.getOutgoing()) { + Index::const_iterator mItr = myMessages.find(messageId); + if (mItr != myMessages.end()) { + myMessages.erase(mItr); + } else { + qDebug() << "Error removing message: it seems to be outgoing yet it wasn't found in outgoing messages"; + } + } else { + if (room) { + + } else { + QString jid = msg.getFromJid(); + std::map::iterator pItr = palMessages.find(jid); + if (pItr != palMessages.end()) { + Index& pMsgs = pItr->second; + Index::const_iterator pmitr = pMsgs.find(messageId); + if (pmitr != pMsgs.end()) { + pMsgs.erase(pmitr); + } else { + qDebug() << "Error removing message: it seems to be incoming yet it wasn't found among messages from that penpal"; + } + } + } + } + ui->deleteLater(); + qDebug() << "message" << messageId << "has been removed"; } else { - itr->second->showError(error); + qDebug() << "An attempt to remove non existing message from messageLine"; } } +void MessageLine::fileError(const QString& messageId, const QString& error) +{ + Index::const_iterator itr = downloading.find(messageId); + if (itr == downloading.end()) { + Index::const_iterator itr = uploading.find(messageId); + if (itr == uploading.end()) { + //TODO may be some logging, that's not normal + } else { + itr->second->showComment(tr("Error uploading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true); + itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload")); + } + } else { + const Shared::Message& msg = itr->second->getMessage(); + itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "" + msg.getOutOfBandUrl() + ""); + itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true); + } +} + +void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path) +{ + message(msg, true); + QString id = msg.getId(); + Message* ui = messageIndex.find(id)->second; + connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload); //this is in case of retry; + ui->setProgress(0); + ui->showComment(tr("Uploading...")); + uploading.insert(std::make_pair(id, ui)); + uploadPaths.insert(std::make_pair(id, path)); + emit uploadFile(msg, path); +} + +void MessageLine::onUpload() +{ + //TODO retry +} diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h index 67280e4..56f0a5e 100644 --- a/ui/utils/messageline.h +++ b/ui/utils/messageline.h @@ -43,24 +43,31 @@ public: MessageLine(bool p_room, QWidget* parent = 0); ~MessageLine(); - Position message(const Shared::Message& msg); + Position message(const Shared::Message& msg, bool forceOutgoing = false); void setMyName(const QString& name); void setPalName(const QString& jid, const QString& name); QString firstMessageId() const; void showBusyIndicator(); void hideBusyIndicator(); void responseLocalFile(const QString& messageId, const QString& path); - void downloadError(const QString& messageId, const QString& error); - void responseDownloadProgress(const QString& messageId, qreal progress); + void fileError(const QString& messageId, const QString& error); + void fileProgress(const QString& messageId, qreal progress); + void appendMessageWithUpload(const Shared::Message& msg, const QString& path); + void removeMessage(const QString& messageId); signals: void resize(int amount); void downloadFile(const QString& messageId, const QString& url); + void uploadFile(const Shared::Message& msg, const QString& path); void requestLocalFile(const QString& messageId, const QString& url); protected: void resizeEvent(QResizeEvent * event) override; +protected: + void onDownload(); + void onUpload(); + private: struct Comparator { bool operator()(const Shared::Message& a, const Shared::Message& b) const { @@ -76,11 +83,13 @@ private: Order messageOrder; Index myMessages; std::map palMessages; + std::map uploadPaths; QVBoxLayout* layout; QString myName; std::map palNames; - std::deque views; + Index uploading; + Index downloading; bool room; bool busyShown; Progress progress; diff --git a/ui/utils/resizer.cpp b/ui/utils/resizer.cpp index 45a21e8..8691400 100644 --- a/ui/utils/resizer.cpp +++ b/ui/utils/resizer.cpp @@ -27,7 +27,8 @@ QObject(parent) bool Resizer::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Resize) { - emit resized(); + QResizeEvent* ev = static_cast(event); + emit resized(ev->oldSize(), ev->size()); } return false; diff --git a/ui/utils/resizer.h b/ui/utils/resizer.h index 85fc97e..735b2fb 100644 --- a/ui/utils/resizer.h +++ b/ui/utils/resizer.h @@ -22,6 +22,7 @@ #include #include #include +#include /** * @todo write docs @@ -35,7 +36,7 @@ protected: bool eventFilter(QObject* obj, QEvent* event) override; signals: - void resized(); + void resized(const QSize& oldSize, const QSize& newSize); }; #endif // RESIZER_H diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 36e7b6e..07a160f 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -36,7 +36,8 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co line(new MessageLine(muc)), m_ui(new Ui::Conversation()), ker(), - res(), + scrollResizeCatcher(), + attachResizeCatcher(), vis(), thread(), statusIcon(0), @@ -60,14 +61,17 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co statusLabel = m_ui->statusLabel; connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); - connect(&res, &Resizer::resized, this, &Conversation::onScrollResize); + connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize); + connect(&attachResizeCatcher, &Resizer::resized, this, &Conversation::onAttachResize); connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize); connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize); connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize); connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile); + connect(line, &MessageLine::uploadFile, this, qOverload(&Conversation::sendMessage)); connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); + connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); m_ui->messageEditor->installEventFilter(&ker); @@ -77,7 +81,8 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co vs->setBackgroundRole(QPalette::Base); vs->setAutoFillBackground(true); connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged); - m_ui->scrollArea->installEventFilter(&res); + m_ui->scrollArea->installEventFilter(&scrollResizeCatcher); + m_ui->filesPanel->installEventFilter(&attachResizeCatcher); applyVisualEffects(); } @@ -184,6 +189,25 @@ void Conversation::onEnterPressed() m_ui->messageEditor->clear(); handleSendMessage(body); } + if (filesToAttach.size() > 0) { + for (Badge* badge : filesToAttach) { + Shared::Message msg; + if (isMuc) { + msg.setType(Shared::Message::groupChat); + } else { + msg.setType(Shared::Message::chat); + msg.setToResource(activePalResource); + } + msg.setFromJid(myJid); + msg.setFromResource(myResource); + msg.setToJid(palJid); + msg.setOutgoing(true); + msg.generateRandomId(); + msg.setCurrentTime(); + line->appendMessageWithUpload(msg, badge->id); + } + clearAttachedFiles(); + } } void Conversation::onMessagesResize(int amount) @@ -294,14 +318,14 @@ void Conversation::onScrollResize() } } -void Conversation::responseDownloadProgress(const QString& messageId, qreal progress) +void Conversation::responseFileProgress(const QString& messageId, qreal progress) { - line->responseDownloadProgress(messageId, progress); + line->fileProgress(messageId, progress); } -void Conversation::downloadError(const QString& messageId, const QString& error) +void Conversation::fileError(const QString& messageId, const QString& error) { - line->downloadError(messageId, error); + line->fileError(messageId, error); } void Conversation::responseLocalFile(const QString& messageId, const QString& path) @@ -352,6 +376,30 @@ void Conversation::clearAttachedFiles() filesLayout->setContentsMargins(0, 0, 0, 0); } +void Conversation::onClearButton() +{ + clearAttachedFiles(); + m_ui->messageEditor->clear(); +} + +void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize) +{ + int oh = oldSize.height(); + int nh = newSize.height(); + + int d = oh - nh; + + if (d != 0) { + QList cs = m_ui->splitter->sizes(); + cs.first() += d; + cs.last() -=d; + + m_ui->splitter->setSizes(cs); + m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum()); + } +} + + bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Show) { diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 5bc2d33..cf9585b 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -75,11 +75,12 @@ public: void responseArchive(const std::list list); void showEvent(QShowEvent * event) override; void responseLocalFile(const QString& messageId, const QString& path); - void downloadError(const QString& messageId, const QString& error); - void responseDownloadProgress(const QString& messageId, qreal progress); + void fileError(const QString& messageId, const QString& error); + void responseFileProgress(const QString& messageId, qreal progress); signals: void sendMessage(const Shared::Message& message); + void sendMessage(const Shared::Message& message, const QString& path); void requestArchive(const QString& before); void shown(); void requestLocalFile(const QString& messageId, const QString& url); @@ -101,7 +102,9 @@ protected slots: void onAttach(); void onFileSelected(); void onScrollResize(); + void onAttachResize(const QSize& oldSize, const QSize& newSize); void onBadgeClose(); + void onClearButton(); public: const bool isMuc; @@ -120,7 +123,8 @@ protected: MessageLine* line; QScopedPointer m_ui; KeyEnterReceiver ker; - Resizer res; + Resizer scrollResizeCatcher; + Resizer attachResizeCatcher; VisibilityCatcher vis; QString thread; QLabel* statusIcon;