1
0
forked from blue/squawk

Merge branch 'fileUpload' of blue/squawk into master

This commit is contained in:
Blue 2019-11-16 18:03:47 +00:00 committed by Gitea
commit 09a946c204
24 changed files with 1045 additions and 329 deletions

View File

@ -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

View File

@ -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
```
git submodule update --init --recursive
```
Then create a folder for the build, go there and build the project using CMake
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
```
mkdir build
cd build
cmake ..
cmake --build .
$ git clone https://git.macaw.me/blue/squawk
$ cd squawk
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
```
#### Building with bundled qxmpp
Here is what you do
```
$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
$ cd squawk
$ mkdir build
$ cd build
$ cmake .. -D SYSTEM_QXMPP=False
$ cmake --build .
```
## License

View File

@ -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<QXmppRosterManager>()),
vm(client.findExtension<QXmppVCardManager>()),
um(new QXmppUploadRequestManager()),
dm(client.findExtension<QXmppDiscoveryManager>()),
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<QString, Contact*>::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<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
RosterItem* ri = 0;
std::map<QString, Contact*>::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<QString, Shared::Message>& 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<QString, Shared::Message>& 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<QString, Shared::Message>::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<QString, Shared::Message>::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)
{
}

View File

@ -31,16 +31,20 @@
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include <QXmppUploadRequestManager.h>
#include <QXmppHttpUploadIq.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#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<QString, QVariant>& 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<QString, QString> 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<QString, Contact*> contacts;
std::map<QString, Conference*> conferences;
unsigned int maxReconnectTimes;
@ -135,10 +144,13 @@ private:
std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts;
std::set<QString> pendingVCardRequests;
std::map<QString, Shared::Message> pendingMessages;
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
QString avatarHash;
QString avatarType;
bool ownVCardRequestInProgress;
NetworkAccess* network;
private slots:
void onClientConnected();
@ -184,6 +196,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);
void handleNewContact(Contact* contact);
@ -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);

View File

@ -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<QString, Download*>::iterator itr = downloads.find(url);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Download* dwn = itr->second;
Transfer* dwn = itr->second;
std::set<QString>::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<QString, Download*>::iterator itr = downloads.find(url);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Download* dwn = itr->second;
Transfer* dwn = itr->second;
std::set<QString>::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<QString, Download*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
for (std::map<QString, Transfer*>::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<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::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,15 +134,28 @@ 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<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::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;
QString errorText = getErrorText(code);
if (errorText.size() > 0) {
itr->second->success = false;
Transfer* dwn = itr->second;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileError(*mItr, errorText);
}
}
}
}
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
{
QString errorText("");
switch (code) {
case QNetworkReply::NoError:
//this never is supposed to happen
@ -256,26 +270,20 @@ void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code)
errorText = "Unknown server error";
break;
}
if (errorText.size() > 0) {
itr->second->success = false;
Download* dwn = itr->second;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileError(*mItr, errorText);
}
}
}
return errorText;
}
void Core::NetworkAccess::onRequestFinished()
void Core::NetworkAccess::onDownloadFinished()
{
QString path("");
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::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::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onRequestError);
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onRequestFinished);
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&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<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::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<QString>::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<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::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<QString>::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<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::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<QString>::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::NetworkError>(&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<QString, Transfer*>::iterator itr = uploads.find(url);
if (itr != uploads.end()) {
Transfer* upl = itr->second;
std::set<QString>::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<QString, QString> headers)
{
QFile* file = new QFile(path);
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
QNetworkRequest req(put);
for (QMap<QString, QString>::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::NetworkError>(&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;
}
}

View File

@ -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<QString, QString> 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<QString, Download*> downloads;
std::map<QString, Transfer*> downloads;
std::map<QString, Transfer*> uploads;
struct Download {
struct Transfer {
std::set<QString> messages;
qreal progress;
QNetworkReply* reply;
bool success;
QString path;
QString url;
QFile* file;
};
};

View File

@ -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<QString, QVariant> 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);

View File

@ -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);

View File

@ -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) {

View File

@ -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;

2
external/qxmpp vendored

@ -1 +1 @@
Subproject commit b18a57daa33f0fefa5f4c63aa7f448b48d302e0d
Subproject commit f8c546c5b701c53d708a38a951fcc734eaee7940

View File

@ -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<const QString&, const Shared::Message&>(&Squawk::sendMessage),
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&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();

View File

@ -1,18 +1,18 @@
# Maintainer: Yury Gubich <blue@macaw.me>
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() {

View File

@ -5,77 +5,92 @@
<name>Account</name>
<message>
<location filename="../ui/widgets/account.ui" line="14"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="127"/>
<source>Account</source>
<translatorcomment>Заголовок окна</translatorcomment>
<translation>Учетная запись</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="40"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="129"/>
<source>Your account login</source>
<translation>Имя пользователя Вашей учетной записи</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="43"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="131"/>
<source>john_smith1987</source>
<translation>ivan_ivanov1987</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="50"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="132"/>
<source>Server</source>
<translation>Сервер</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="57"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="134"/>
<source>A server address of your account. Like 404.city or macaw.me</source>
<translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="60"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="136"/>
<source>macaw.me</source>
<translation>macaw.me</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="67"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="137"/>
<source>Login</source>
<translation>Имя учетной записи</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="74"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="138"/>
<source>Password</source>
<translation>Пароль</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="81"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="140"/>
<source>Password of your account</source>
<translation>Пароль вашей учетной записи</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="103"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="145"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="110"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="147"/>
<source>Just a name how would you call this account, doesn&apos;t affect anything</source>
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="113"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="149"/>
<source>John</source>
<translation>Иван</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="120"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="150"/>
<source>Resource</source>
<translation>Ресурс</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="127"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="152"/>
<source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
<translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
</message>
<message>
<location filename="../ui/widgets/account.ui" line="130"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="154"/>
<source>QXmpp</source>
<translatorcomment>Ресурс по умолчанию</translatorcomment>
<translation>QXmpp</translation>
@ -85,31 +100,37 @@
<name>Accounts</name>
<message>
<location filename="../ui/widgets/accounts.ui" line="14"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="108"/>
<source>Accounts</source>
<translation>Учетные записи</translation>
</message>
<message>
<location filename="../ui/widgets/accounts.ui" line="45"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="109"/>
<source>Delete</source>
<translation>Удалить</translation>
</message>
<message>
<location filename="../ui/widgets/accounts.ui" line="86"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="110"/>
<source>Add</source>
<translation>Добавить</translation>
</message>
<message>
<location filename="../ui/widgets/accounts.ui" line="96"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="111"/>
<source>Edit</source>
<translation>Редактировать</translation>
</message>
<message>
<location filename="../ui/widgets/accounts.ui" line="106"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="112"/>
<source>Change password</source>
<translation>Изменить пароль</translation>
</message>
<message>
<location filename="../ui/widgets/accounts.ui" line="129"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="113"/>
<location filename="../ui/widgets/accounts.cpp" line="125"/>
<location filename="../ui/widgets/accounts.cpp" line="128"/>
<source>Connect</source>
@ -125,11 +146,12 @@
<name>Conversation</name>
<message>
<location filename="../ui/widgets/conversation.ui" line="449"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_conversation.h" line="324"/>
<source>Type your message here...</source>
<translation>Введите сообщение...</translation>
</message>
<message>
<location filename="../ui/widgets/conversation.cpp" line="260"/>
<location filename="../ui/widgets/conversation.cpp" line="284"/>
<source>Chose a file to send</source>
<translation>Выберите файл для отправки</translation>
</message>
@ -281,52 +303,62 @@
<name>JoinConference</name>
<message>
<location filename="../ui/widgets/joinconference.ui" line="14"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="116"/>
<source>Join new conference</source>
<translatorcomment>Заголовок окна</translatorcomment>
<translation>Присоединиться к новой беседе</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="22"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="117"/>
<source>JID</source>
<translation>JID</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="29"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="119"/>
<source>Room JID</source>
<translation>Jabber-идентификатор беседы</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="32"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="121"/>
<source>identifier@conference.server.org</source>
<translation>identifier@conference.server.org</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="39"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="122"/>
<source>Account</source>
<translation>Учетная запись</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="49"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="123"/>
<source>Join on login</source>
<translation>Автовход</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="56"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="125"/>
<source>If checked Squawk will try to join this conference on login</source>
<translation>Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="66"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="128"/>
<source>Nick name</source>
<translation>Псевдоним</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="73"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="130"/>
<source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
<translation>Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи</translation>
</message>
<message>
<location filename="../ui/widgets/joinconference.ui" line="76"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="132"/>
<source>John</source>
<translation>Ivan</translation>
</message>
@ -334,26 +366,58 @@
<context>
<name>Message</name>
<message>
<location filename="../ui/utils/message.cpp" line="119"/>
<source>Download</source>
<translation>Скачать</translation>
</message>
<message>
<location filename="../ui/utils/message.cpp" line="123"/>
<location filename="../ui/utils/message.cpp" line="172"/>
<source>Open</source>
<translation>Открыть</translation>
</message>
</context>
<context>
<name>MessageLine</name>
<message>
<location filename="../ui/utils/messageline.cpp" line="146"/>
<source>Downloading...</source>
<translation>Скачивается...</translation>
</message>
<message>
<location filename="../ui/utils/messageline.cpp" line="258"/>
<location filename="../ui/utils/messageline.cpp" line="324"/>
<source>Download</source>
<translation>Скачать</translation>
</message>
<message>
<location filename="../ui/utils/messageline.cpp" line="259"/>
<source>Push the button to daownload the file</source>
<translation>Нажмите на кнопку что бы загрузить файл</translation>
</message>
<message>
<location filename="../ui/utils/messageline.cpp" line="319"/>
<source>Error uploading file: %1
You can try again</source>
<translation>Ошибка загрузки файла на сервер:
%1
Для того, что бы попробовать снова нажмите на кнопку</translation>
</message>
<message>
<location filename="../ui/utils/messageline.cpp" line="320"/>
<source>Upload</source>
<translation>Загрузить</translation>
</message>
<message>
<location filename="../ui/utils/messageline.cpp" line="325"/>
<source>Error downloading file: %1
You can try again</source>
<translation>Ошибка загрузки файла: %1
<translation>Ошибка скачивания файла:
%1
Вы можете попробовать снова</translation>
</message>
<message>
<location filename="../ui/utils/message.cpp" line="125"/>
<source>%1 is offering you to download a file</source>
<translation>%1 предлагает Вам скачать файл</translation>
</message>
<message>
<location filename="../ui/utils/message.cpp" line="190"/>
<source>Open</source>
<translation>Открыть</translation>
<location filename="../ui/utils/messageline.cpp" line="336"/>
<source>Uploading...</source>
<translation>Загружается...</translation>
</message>
</context>
<context>
@ -470,48 +534,57 @@ You can try again</source>
<name>NewContact</name>
<message>
<location filename="../ui/widgets/newcontact.ui" line="14"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="103"/>
<source>Add new contact</source>
<translatorcomment>Заголовок окна</translatorcomment>
<translation>Добавление нового контакта</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="22"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="104"/>
<source>Account</source>
<translation>Учетная запись</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="29"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="106"/>
<source>An account that is going to have new contact</source>
<translation>Учетная запись для которой будет добавлен контакт</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="36"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="108"/>
<source>JID</source>
<translation>JID</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="43"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="110"/>
<source>Jabber id of your new contact</source>
<translation>Jabber-идентификатор нового контакта</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="46"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="112"/>
<source>name@server.dmn</source>
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
<translation>name@server.dmn</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="53"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="113"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="60"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="115"/>
<source>The way this new contact will be labeled in your roster (optional)</source>
<translation>То, как будет подписан контакт в вашем списке контактов (не обязательно)</translation>
</message>
<message>
<location filename="../ui/widgets/newcontact.ui" line="63"/>
<location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="117"/>
<source>John Smith</source>
<translation>Иван Иванов</translation>
</message>
@ -520,36 +593,43 @@ You can try again</source>
<name>Squawk</name>
<message>
<location filename="../ui/squawk.ui" line="14"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="137"/>
<source>squawk</source>
<translation>Squawk</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="78"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="143"/>
<source>Settings</source>
<translation>Настройки</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="84"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="144"/>
<source>Squawk</source>
<translation>Squawk</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="99"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="138"/>
<source>Accounts</source>
<translation>Учетные записи</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="108"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="139"/>
<source>Quit</source>
<translation>Выйти</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="120"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="140"/>
<source>Add contact</source>
<translation>Добавить контакт</translation>
</message>
<message>
<location filename="../ui/squawk.ui" line="132"/>
<location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="141"/>
<source>Add conference</source>
<translation>Присоединиться к беседе</translation>
</message>
@ -559,52 +639,52 @@ You can try again</source>
<translation>Список контактов</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="549"/>
<location filename="../ui/squawk.cpp" line="558"/>
<source>Disconnect</source>
<translation>Отключить</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="555"/>
<location filename="../ui/squawk.cpp" line="564"/>
<source>Connect</source>
<translation>Подключить</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="561"/>
<location filename="../ui/squawk.cpp" line="659"/>
<location filename="../ui/squawk.cpp" line="570"/>
<location filename="../ui/squawk.cpp" line="668"/>
<source>VCard</source>
<translation>Карточка</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="565"/>
<location filename="../ui/squawk.cpp" line="663"/>
<location filename="../ui/squawk.cpp" line="703"/>
<location filename="../ui/squawk.cpp" line="574"/>
<location filename="../ui/squawk.cpp" line="672"/>
<location filename="../ui/squawk.cpp" line="712"/>
<source>Remove</source>
<translation>Удалить</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="577"/>
<location filename="../ui/squawk.cpp" line="586"/>
<source>Open dialog</source>
<translation>Открыть диалог</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="587"/>
<location filename="../ui/squawk.cpp" line="684"/>
<location filename="../ui/squawk.cpp" line="596"/>
<location filename="../ui/squawk.cpp" line="693"/>
<source>Unsubscribe</source>
<translation>Отписаться</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="597"/>
<location filename="../ui/squawk.cpp" line="693"/>
<location filename="../ui/squawk.cpp" line="606"/>
<location filename="../ui/squawk.cpp" line="702"/>
<source>Subscribe</source>
<translation>Подписаться</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="608"/>
<location filename="../ui/squawk.cpp" line="617"/>
<source>Rename</source>
<translation>Переименовать</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="621"/>
<location filename="../ui/squawk.cpp" line="630"/>
<source>Input new name for %1
or leave it empty for the contact
to be displayed as %1</source>
@ -614,47 +694,47 @@ to be displayed as %1</source>
%1</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="622"/>
<location filename="../ui/squawk.cpp" line="631"/>
<source>Renaming %1</source>
<translation>Назначение имени контакту %1</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="628"/>
<location filename="../ui/squawk.cpp" line="637"/>
<source>Groups</source>
<translation>Группы</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="643"/>
<location filename="../ui/squawk.cpp" line="652"/>
<source>New group</source>
<translation>Создать новую группу</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="653"/>
<location filename="../ui/squawk.cpp" line="662"/>
<source>New group name</source>
<translation>Имя группы</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="654"/>
<location filename="../ui/squawk.cpp" line="663"/>
<source>Add %1 to a new group</source>
<translation>Добавление %1 в новую группу</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="675"/>
<location filename="../ui/squawk.cpp" line="684"/>
<source>Open conversation</source>
<translation>Открыть окно беседы</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="780"/>
<location filename="../ui/squawk.cpp" line="789"/>
<source>%1 account card</source>
<translation>Карточка учетной записи %1</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="782"/>
<location filename="../ui/squawk.cpp" line="791"/>
<source>%1 contact card</source>
<translation>Карточка контакта %1</translation>
</message>
<message>
<location filename="../ui/squawk.cpp" line="794"/>
<location filename="../ui/squawk.cpp" line="803"/>
<source>Downloading vCard</source>
<translation>Получение карточки</translation>
</message>
@ -663,119 +743,145 @@ to be displayed as %1</source>
<name>VCard</name>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="612"/>
<source>Received 12.07.2007 at 17.35</source>
<translation>Не обновлялось</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
<location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="624"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="627"/>
<source>General</source>
<translation>Общее</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="613"/>
<source>Organization</source>
<translation>Место работы</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="614"/>
<source>Middle name</source>
<translation>Среднее имя</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="615"/>
<source>First name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="616"/>
<source>Last name</source>
<translation>Фамилия</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="617"/>
<source>Nick name</source>
<translation>Псевдоним</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="618"/>
<source>Birthday</source>
<translation>Дата рождения</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="619"/>
<source>Organization name</source>
<translation>Название организации</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="620"/>
<source>Unit / Department</source>
<translation>Отдел</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="621"/>
<source>Role / Profession</source>
<translation>Профессия</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="622"/>
<source>Job title</source>
<translation>Наименование должности</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="623"/>
<source>Full name</source>
<translation>Полное имя</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="625"/>
<source>Personal information</source>
<translation>Личная информация</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="629"/>
<source>Addresses</source>
<translation>Адреса</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="630"/>
<source>E-Mail addresses</source>
<translation>Адреса электронной почты</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="633"/>
<source>Phone numbers</source>
<translation>Номера телефонов</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
<location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="628"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="634"/>
<source>Contact</source>
<translation>Контактная информация</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="631"/>
<source>Jabber ID</source>
<translation>Jabber ID</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="632"/>
<source>Web site</source>
<translation>Веб сайт</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
<location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="635"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="636"/>
<source>Description</source>
<translation>Описание</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="610"/>
<source>Set avatar</source>
<translation>Установить иконку</translation>
</message>
<message>
<location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
<location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="611"/>
<source>Clear avatar</source>
<translation>Убрать иконку</translation>
</message>

View File

@ -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<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
this, qOverload<const Shared::Message&, const QString&>(&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<QString, std::set<Models::Roster::ElId>>::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<Models::Roster::ElId>& 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<QString, std::set<Models::Roster::ElId>>::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<Models::Roster::ElId>& 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<Conversation*>(sender());
emit sendMessage(conv->getAccount(), msg);
}
void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
{
Conversation* conv = static_cast<Conversation*>(sender());
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).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<Conversation*>(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();

View File

@ -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<QString, QVariant>& 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();

View File

@ -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("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
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()));
button = new QPushButton(icon, buttonText);
button->setToolTip(tooltip);
connect(button, &QPushButton::clicked, this, &Message::buttonClicked);
bodyLayout->insertWidget(2, button);
hasButton = true;
}
fileComment->show();
connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload);
bodyLayout->insertWidget(2, fileComment);
bodyLayout->insertWidget(3, downloadButton);
hasDownloadButton = true;
commentAdded = 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;
}

View File

@ -30,9 +30,9 @@
#include <QDesktopServices>
#include <QUrl>
#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

View File

@ -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,18 +59,22 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
QString sender;
bool outgoing;
if (forceOutgoing) {
sender = myName;
outgoing = true;
} else {
if (room) {
if (msg.getFromResource() == myName) {
sender = myName;
outgoing = false;
outgoing = true;
} else {
sender = msg.getFromResource();
outgoing = true;
outgoing = false;
}
} else {
if (msg.getOutgoing()) {
sender = myName;
outgoing = false;
outgoing = true;
} else {
QString jid = msg.getFromJid();
std::map<QString, QString>::iterator itr = palNames.find(jid);
@ -77,7 +83,8 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
} else {
sender = jid;
}
outgoing = true;
outgoing = false;
}
}
}
@ -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<Order::const_iterator>(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<Message*>(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) {
Index::const_iterator dItr = downloading.find(messageId);
if (dItr != downloading.end()) {
downloading.erase(dItr);
itr->second->showFile(path);
} else {
itr->second->addDownloadDialog();
if (uItr != uploading.end()) {
uploading.erase(uItr);
std::map<QString, QString>::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 {
if (uItr == uploading.end()) {
const Shared::Message& msg = itr->second->getMessage();
itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
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 {
itr->second->showError(error);
QString jid = msg.getFromJid();
std::map<QString, Index>::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 {
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"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
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
}

View File

@ -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<QString, Index> palMessages;
std::map<QString, QString> uploadPaths;
QVBoxLayout* layout;
QString myName;
std::map<QString, QString> palNames;
std::deque<QHBoxLayout*> views;
Index uploading;
Index downloading;
bool room;
bool busyShown;
Progress progress;

View File

@ -27,7 +27,8 @@ QObject(parent)
bool Resizer::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Resize) {
emit resized();
QResizeEvent* ev = static_cast<QResizeEvent*>(event);
emit resized(ev->oldSize(), ev->size());
}
return false;

View File

@ -22,6 +22,7 @@
#include <QObject>
#include <QWidget>
#include <QEvent>
#include <QResizeEvent>
/**
* @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

View File

@ -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<const Shared::Message&, const QString&>(&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<int> 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) {

View File

@ -75,11 +75,12 @@ public:
void responseArchive(const std::list<Shared::Message> 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<Ui::Conversation> m_ui;
KeyEnterReceiver ker;
Resizer res;
Resizer scrollResizeCatcher;
Resizer attachResizeCatcher;
VisibilityCatcher vis;
QString thread;
QLabel* statusIcon;