Browse Source

Merge branch 'fileUpload' of blue/squawk into master

pull/39/head 0.1.1
Blue 2 years ago
committed by Gitea
parent
commit
09a946c204
  1. 9
      CMakeLists.txt
  2. 46
      README.md
  3. 132
      core/account.cpp
  4. 25
      core/account.h
  5. 438
      core/networkaccess.cpp
  6. 27
      core/networkaccess.h
  7. 18
      core/squawk.cpp
  8. 3
      core/squawk.h
  9. 29
      core/storage.cpp
  10. 1
      core/storage.h
  11. 2
      external/qxmpp
  12. 16
      main.cpp
  13. 8
      packaging/Archlinux/PKGBUILD
  14. 176
      translations/squawk.ru.ts
  15. 27
      ui/squawk.cpp
  16. 6
      ui/squawk.h
  17. 101
      ui/utils/message.cpp
  18. 27
      ui/utils/message.h
  19. 184
      ui/utils/messageline.cpp
  20. 17
      ui/utils/messageline.h
  21. 3
      ui/utils/resizer.cpp
  22. 3
      ui/utils/resizer.h
  23. 62
      ui/widgets/conversation.cpp
  24. 10
      ui/widgets/conversation.h

9
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

46
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

132
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<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)
{
}

25
core/account.h

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

438
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<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,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<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;
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<QString>::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<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;
}
}

27
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<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;
};
};

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

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

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

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

2
external/qxmpp

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

16
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<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();

8
packaging/Archlinux/PKGBUILD

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

176
translations/squawk.ru.ts

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