mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge pull request #3536
Diadlo (9): refactor(videomode): Move implementation in cpp file docs(audio, video): Change comment style docs(chatlog): Change comment style docs(net): Change comment style docs(widget): Change comment style docs(persistence): Change comment style docs(core): Change comment style feat(doxygen): Created simple doxygen config file docs(CONTRIBUTING.md): Added documntation block
This commit is contained in:
commit
f61fbfec14
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -12,3 +12,4 @@ build-*-Release
|
|||
build-*-Profile
|
||||
build-*-Debug
|
||||
.DS_Store
|
||||
doc/html/*
|
||||
|
|
|
@ -197,6 +197,57 @@ QObject notToMentionThatWeUseCamelCase;
|
|||
|
||||
E.g. https://github.com/tux3/qTox/blob/master/src/misc/flowlayout.cpp
|
||||
|
||||
## Documentaion
|
||||
|
||||
If you added a new function, also add a doxygen comment before the implementation.
|
||||
If you changed an old function, make sure the doxygen comment is still correct.
|
||||
If it doesn't exist add it.
|
||||
|
||||
Don't put docs in .h files, if there is a corresponding .cpp file.
|
||||
|
||||
### Documentation style
|
||||
|
||||
```C++
|
||||
/*...license info...*/
|
||||
#include "blabla.h"
|
||||
|
||||
/**
|
||||
I can be briefly described as well!
|
||||
*/
|
||||
static void method()
|
||||
{
|
||||
// I'm just a little example.
|
||||
}
|
||||
|
||||
/**
|
||||
@class OurClass
|
||||
@brief Exists for some reason...!?
|
||||
|
||||
Longer description
|
||||
*/
|
||||
|
||||
/**
|
||||
@enum OurClass::OurEnum
|
||||
@brief The brief description line.
|
||||
|
||||
@var EnumValue1
|
||||
means something
|
||||
|
||||
@var EnumValue2
|
||||
means something else
|
||||
|
||||
Optional long description
|
||||
*/
|
||||
|
||||
/**
|
||||
@fn OurClass::somethingHappened(const QString &happened)
|
||||
@param[in] happened tells what has happened...
|
||||
@brief This signal is emitted when something has happened in the class.
|
||||
|
||||
Here's an optional longer description of what the signal additionally does.
|
||||
*/
|
||||
```
|
||||
|
||||
## No translatable HTML tags
|
||||
|
||||
Do not put HTML in UI files, or inside `tr()`. Instead, you can put put it in
|
||||
|
|
20
doxygen.conf
Normal file
20
doxygen.conf
Normal file
|
@ -0,0 +1,20 @@
|
|||
# General
|
||||
PROJECT_NAME = qTox
|
||||
EXTRACT_ALL = YES
|
||||
EXTRACT_PRIVATE = YES
|
||||
SOURCE_BROWSER = YES
|
||||
GENERATE_TODOLIST = YES
|
||||
GENERATE_TESTLIST = YES
|
||||
|
||||
# Input
|
||||
INPUT = src
|
||||
FILE_PATTERNS = *.h *.cpp
|
||||
RECURSIVE = YES
|
||||
|
||||
# Output
|
||||
OUTPUT_DIRECTORY = doc
|
||||
OUTPUT_LANGUAGE = English
|
||||
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_HTML = YES
|
||||
HTML_OUTPUT = html
|
3
qtox.pro
3
qtox.pro
|
@ -472,4 +472,5 @@ SOURCES += \
|
|||
src/widget/about/aboutuser.cpp \
|
||||
src/widget/form/groupinviteform.cpp \
|
||||
src/widget/tool/profileimporter.cpp \
|
||||
src/widget/passwordedit.cpp
|
||||
src/widget/passwordedit.cpp \
|
||||
src/video/videomode.cpp
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
#include <cassert>
|
||||
|
||||
/**
|
||||
@internal
|
||||
|
||||
@class Audio::Private
|
||||
|
||||
@brief Encapsulates private audio framework from public qTox Audio API.
|
||||
|
@ -88,7 +86,28 @@ private:
|
|||
};
|
||||
|
||||
/**
|
||||
Returns the singleton instance.
|
||||
@class Audio
|
||||
|
||||
@fn void Audio::frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate);
|
||||
|
||||
When there are input subscribers, we regularly emit captured audio frames with this signal
|
||||
Always connect with a blocking queued connection lambda, else the behaviour is undefined
|
||||
|
||||
@var Audio::AUDIO_SAMPLE_RATE
|
||||
@brief The next best Opus would take is 24k
|
||||
|
||||
@var Audio::AUDIO_FRAME_DURATION
|
||||
@brief In milliseconds
|
||||
|
||||
@var Audio::AUDIO_FRAME_SAMPLE_COUNT
|
||||
@brief Frame sample count
|
||||
|
||||
@var Audio::AUDIO_CHANNELS
|
||||
@brief Ideally, we'd auto-detect, but that's a sane default
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
Audio& Audio::getInstance()
|
||||
{
|
||||
|
@ -150,7 +169,7 @@ void Audio::checkAlcError(ALCdevice *device) noexcept
|
|||
}
|
||||
|
||||
/**
|
||||
Returns the current output volume (between 0 and 1)
|
||||
@brief Returns the current output volume (between 0 and 1)
|
||||
*/
|
||||
qreal Audio::outputVolume() const
|
||||
{
|
||||
|
@ -168,7 +187,7 @@ qreal Audio::outputVolume() const
|
|||
}
|
||||
|
||||
/**
|
||||
Set the master output volume.
|
||||
@brief Set the master output volume.
|
||||
|
||||
@param[in] volume the master volume (between 0 and 1)
|
||||
*/
|
||||
|
@ -238,7 +257,7 @@ qreal Audio::inputGain() const
|
|||
}
|
||||
|
||||
/**
|
||||
Set the input gain dB level.
|
||||
@brief Set the input gain dB level.
|
||||
*/
|
||||
void Audio::setInputGain(qreal dB)
|
||||
{
|
||||
|
@ -299,7 +318,7 @@ void Audio::unsubscribeInput()
|
|||
}
|
||||
|
||||
/**
|
||||
Initialize audio input device, if not initialized.
|
||||
@brief Initialize audio input device, if not initialized.
|
||||
|
||||
@return true, if device was initialized; false otherwise
|
||||
*/
|
||||
|
@ -309,7 +328,7 @@ bool Audio::autoInitInput()
|
|||
}
|
||||
|
||||
/**
|
||||
Initialize audio output device, if not initialized.
|
||||
@brief Initialize audio output device, if not initialized.
|
||||
|
||||
@return true, if device was initialized; false otherwise
|
||||
*/
|
||||
|
@ -354,9 +373,7 @@ bool Audio::initInput(const QString& deviceName)
|
|||
}
|
||||
|
||||
/**
|
||||
@internal
|
||||
|
||||
Open an audio output device
|
||||
@brief Open an audio output device
|
||||
*/
|
||||
bool Audio::initOutput(const QString& deviceName)
|
||||
{
|
||||
|
@ -409,7 +426,7 @@ bool Audio::initOutput(const QString& deviceName)
|
|||
}
|
||||
|
||||
/**
|
||||
Play a 44100Hz mono 16bit PCM sound from a file
|
||||
@brief Play a 44100Hz mono 16bit PCM sound from a file
|
||||
*/
|
||||
void Audio::playMono16Sound(const QString& path)
|
||||
{
|
||||
|
@ -419,7 +436,7 @@ void Audio::playMono16Sound(const QString& path)
|
|||
}
|
||||
|
||||
/**
|
||||
Play a 44100Hz mono 16bit PCM sound
|
||||
@brief Play a 44100Hz mono 16bit PCM sound
|
||||
*/
|
||||
void Audio::playMono16Sound(const QByteArray& data)
|
||||
{
|
||||
|
@ -488,9 +505,7 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
|
|||
}
|
||||
|
||||
/**
|
||||
@internal
|
||||
|
||||
Close active audio input device.
|
||||
@brief Close active audio input device.
|
||||
*/
|
||||
void Audio::cleanupInput()
|
||||
{
|
||||
|
@ -506,9 +521,7 @@ void Audio::cleanupInput()
|
|||
}
|
||||
|
||||
/**
|
||||
@internal
|
||||
|
||||
Close active audio output device
|
||||
@brief Close active audio output device
|
||||
*/
|
||||
void Audio::cleanupOutput()
|
||||
{
|
||||
|
@ -540,6 +553,9 @@ void Audio::cleanupOutput()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Called after a mono16 sound stopped playing
|
||||
*/
|
||||
void Audio::playMono16SoundCleanup()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
@ -554,6 +570,9 @@ void Audio::playMono16SoundCleanup()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Called on the captureTimer events to capture audio
|
||||
*/
|
||||
void Audio::doCapture()
|
||||
{
|
||||
QMutexLocker lock(&audioLock);
|
||||
|
@ -583,7 +602,7 @@ void Audio::doCapture()
|
|||
}
|
||||
|
||||
/**
|
||||
Returns true if the output device is open
|
||||
@brief Returns true if the output device is open
|
||||
*/
|
||||
bool Audio::isOutputReady() const
|
||||
{
|
||||
|
|
|
@ -42,13 +42,6 @@
|
|||
#include <AL/alext.h>
|
||||
#endif
|
||||
|
||||
|
||||
// Public default audio settings
|
||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000; ///< The next best Opus would take is 24k
|
||||
static constexpr uint32_t AUDIO_FRAME_DURATION = 20; ///< In milliseconds
|
||||
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
|
||||
static constexpr uint32_t AUDIO_CHANNELS = 2; ///< Ideally, we'd auto-detect, but that's a sane default
|
||||
|
||||
class Audio : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -92,10 +85,15 @@ public:
|
|||
void playAudioBuffer(ALuint alSource, const int16_t *data, int samples,
|
||||
unsigned channels, int sampleRate);
|
||||
|
||||
public:
|
||||
// Public default audio settings
|
||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
|
||||
static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
|
||||
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
|
||||
static constexpr uint32_t AUDIO_CHANNELS = 2;
|
||||
|
||||
signals:
|
||||
void groupAudioPlayed(int group, int peer, unsigned short volume);
|
||||
/// When there are input subscribers, we regularly emit captured audio frames with this signal
|
||||
/// Always connect with a blocking queued connection or a lambda, or the behavior is undefined
|
||||
void frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate);
|
||||
|
||||
private:
|
||||
|
@ -111,12 +109,9 @@ private:
|
|||
bool initOutput(const QString& outDevDescr);
|
||||
void cleanupInput();
|
||||
void cleanupOutput();
|
||||
/// Called after a mono16 sound stopped playing
|
||||
void playMono16SoundCleanup();
|
||||
/// Called on the captureTimer events to capture audio
|
||||
void doCapture();
|
||||
|
||||
|
||||
private:
|
||||
Private* d;
|
||||
|
||||
|
|
|
@ -24,6 +24,14 @@
|
|||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
@enum ChatLineContentProxy::ChatLineContentProxyType
|
||||
@brief Type tag to avoid dynamic_cast of contained QWidget*
|
||||
|
||||
@value GenericType
|
||||
@value FileTransferWidgetType = 0
|
||||
*/
|
||||
|
||||
ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent)
|
||||
: widthPercent(widthInPercent)
|
||||
, widthMin(minWidth)
|
||||
|
|
|
@ -28,7 +28,6 @@ class FileTransferWidget;
|
|||
class ChatLineContentProxy : public ChatLineContent
|
||||
{
|
||||
public:
|
||||
// Type tag to avoid dynamic_cast of contained QWidget*
|
||||
enum ChatLineContentProxyType
|
||||
{
|
||||
GenericType,
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QShortcut>
|
||||
|
||||
/**
|
||||
@var ChatLog::repNameAfter
|
||||
@brief repetition interval sender name (sec)
|
||||
*/
|
||||
|
||||
template<class T>
|
||||
T clamp(T x, T min, T max)
|
||||
{
|
||||
|
|
|
@ -62,7 +62,6 @@ public:
|
|||
ChatLine::Ptr getTypingNotification() const;
|
||||
QVector<ChatLine::Ptr> getLines();
|
||||
ChatLine::Ptr getLatestLine() const;
|
||||
// repetition interval sender name (sec)
|
||||
const uint repNameAfter = 5*60;
|
||||
|
||||
signals:
|
||||
|
|
|
@ -43,6 +43,9 @@ void DocumentCache::push(QTextDocument *doc)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
DocumentCache &DocumentCache::getInstance()
|
||||
{
|
||||
static DocumentCache instance;
|
||||
|
|
|
@ -35,6 +35,9 @@ QPixmap PixmapCache::get(const QString &filename, QSize size)
|
|||
return itr.value().pixmap(size);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
PixmapCache &PixmapCache::getInstance()
|
||||
{
|
||||
static PixmapCache instance;
|
||||
|
|
|
@ -104,6 +104,9 @@ Core::~Core()
|
|||
deadifyTox();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the global widget's Core instance
|
||||
*/
|
||||
Core* Core::getInstance()
|
||||
{
|
||||
return Nexus::getCore();
|
||||
|
@ -217,6 +220,9 @@ void Core::makeTox(QByteArray savedata)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Initializes the core, must be called before anything else
|
||||
*/
|
||||
void Core::start()
|
||||
{
|
||||
bool isNewProfile = profile.isNewProfile();
|
||||
|
@ -329,6 +335,9 @@ void Core::start()
|
|||
*/
|
||||
#define CORE_DISCONNECT_TOLERANCE 30
|
||||
|
||||
/**
|
||||
@brief Processes toxcore events and ensure we stay connected, called by its own timer
|
||||
*/
|
||||
void Core::process()
|
||||
{
|
||||
if (!isReady())
|
||||
|
@ -383,6 +392,9 @@ bool Core::checkConnection()
|
|||
return isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Connects us to the Tox network
|
||||
*/
|
||||
void Core::bootstrapDht()
|
||||
{
|
||||
const Settings& s = Settings::getInstance();
|
||||
|
@ -720,6 +732,9 @@ void Core::removeGroup(int groupId, bool fake)
|
|||
av->leaveGroupCall(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns our username, or an empty string on failure
|
||||
*/
|
||||
QString Core::getUsername() const
|
||||
{
|
||||
QString sname;
|
||||
|
@ -769,6 +784,9 @@ void Core::setAvatar(const QByteArray& data)
|
|||
AvatarBroadcaster::enableAutoBroadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns our Tox ID
|
||||
*/
|
||||
ToxId Core::getSelfId() const
|
||||
{
|
||||
uint8_t friendAddress[TOX_ADDRESS_SIZE] = {0};
|
||||
|
@ -776,6 +794,9 @@ ToxId Core::getSelfId() const
|
|||
return ToxId(CFriendAddress::toString(friendAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns our public and private keys
|
||||
*/
|
||||
QPair<QByteArray, QByteArray> Core::getKeypair() const
|
||||
{
|
||||
QPair<QByteArray, QByteArray> keypair;
|
||||
|
@ -790,6 +811,9 @@ QPair<QByteArray, QByteArray> Core::getKeypair() const
|
|||
return keypair;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns our status message, or an empty string on failure
|
||||
*/
|
||||
QString Core::getStatusMessage() const
|
||||
{
|
||||
QString sname;
|
||||
|
@ -803,6 +827,9 @@ QString Core::getStatusMessage() const
|
|||
return sname;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns our user status
|
||||
*/
|
||||
Status Core::getStatus() const
|
||||
{
|
||||
return (Status)tox_self_get_status(tox);
|
||||
|
@ -867,6 +894,9 @@ QString Core::sanitize(QString name)
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the unencrypted tox save data
|
||||
*/
|
||||
QByteArray Core::getToxSaveData()
|
||||
{
|
||||
uint32_t fileSize = tox_get_savedata_size(tox);
|
||||
|
@ -925,6 +955,9 @@ void Core::checkLastOnline(uint32_t friendId) {
|
|||
emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the list of friendIds in our friendlist, an empty list on error
|
||||
*/
|
||||
QVector<uint32_t> Core::getFriendList() const
|
||||
{
|
||||
QVector<uint32_t> friends;
|
||||
|
@ -933,11 +966,17 @@ QVector<uint32_t> Core::getFriendList() const
|
|||
return friends;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Return the number of peers in the group chat on success, or -1 on failure
|
||||
*/
|
||||
int Core::getGroupNumberPeers(int groupId) const
|
||||
{
|
||||
return tox_group_number_peers(tox, groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the name of a peer of a group
|
||||
*/
|
||||
QString Core::getGroupPeerName(int groupId, int peerId) const
|
||||
{
|
||||
QString name;
|
||||
|
@ -952,6 +991,9 @@ QString Core::getGroupPeerName(int groupId, int peerId) const
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the public key of a peer of a group
|
||||
*/
|
||||
ToxId Core::getGroupPeerToxId(int groupId, int peerId) const
|
||||
{
|
||||
ToxId peerToxId;
|
||||
|
@ -968,6 +1010,9 @@ ToxId Core::getGroupPeerToxId(int groupId, int peerId) const
|
|||
return peerToxId;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the names of the peers of a group
|
||||
*/
|
||||
QList<QString> Core::getGroupPeerNames(int groupId) const
|
||||
{
|
||||
QList<QString> names;
|
||||
|
@ -999,6 +1044,9 @@ QList<QString> Core::getGroupPeerNames(int groupId) const
|
|||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Accept a groupchat invite
|
||||
*/
|
||||
int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* friend_group_public_key,uint16_t length) const
|
||||
{
|
||||
if (type == TOX_GROUPCHAT_TYPE_TEXT)
|
||||
|
@ -1020,6 +1068,9 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Quit a groupchat
|
||||
*/
|
||||
void Core::quitGroupChat(int groupId) const
|
||||
{
|
||||
tox_del_groupchat(tox, groupId);
|
||||
|
@ -1052,11 +1103,17 @@ int Core::createGroup(uint8_t type)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if a friend is online. Unknown friends are considered offline.
|
||||
*/
|
||||
bool Core::isFriendOnline(uint32_t friendId) const
|
||||
{
|
||||
return tox_friend_get_connection_status(tox, friendId, nullptr) != TOX_CONNECTION_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if we have a friend by address
|
||||
*/
|
||||
bool Core::hasFriendWithAddress(const QString &addr) const
|
||||
{
|
||||
// Valid length check
|
||||
|
@ -1069,6 +1126,9 @@ bool Core::hasFriendWithAddress(const QString &addr) const
|
|||
return hasFriendWithPublicKey(pubkey);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if we have a friend by public key
|
||||
*/
|
||||
bool Core::hasFriendWithPublicKey(const QString &pubkey) const
|
||||
{
|
||||
// Valid length check
|
||||
|
@ -1100,6 +1160,9 @@ bool Core::hasFriendWithPublicKey(const QString &pubkey) const
|
|||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the full address if known, or public key of a friend
|
||||
*/
|
||||
QString Core::getFriendAddress(uint32_t friendNumber) const
|
||||
{
|
||||
QString id = getFriendPublicKey(friendNumber);
|
||||
|
@ -1110,6 +1173,9 @@ QString Core::getFriendAddress(uint32_t friendNumber) const
|
|||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the public key part of the ToxID only
|
||||
*/
|
||||
QString Core::getFriendPublicKey(uint32_t friendNumber) const
|
||||
{
|
||||
uint8_t rawid[TOX_PUBLIC_KEY_SIZE];
|
||||
|
@ -1124,6 +1190,9 @@ QString Core::getFriendPublicKey(uint32_t friendNumber) const
|
|||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the username of a friend
|
||||
*/
|
||||
QString Core::getFriendUsername(uint32_t friendnumber) const
|
||||
{
|
||||
size_t namesize = tox_friend_get_name_size(tox, friendnumber, nullptr);
|
||||
|
@ -1197,6 +1266,9 @@ QString Core::getPeerName(const ToxId& id) const
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Most of the API shouldn't be used until Core is ready, call start() first
|
||||
*/
|
||||
bool Core::isReady()
|
||||
{
|
||||
return av && av->getToxAv() && tox && ready;
|
||||
|
@ -1211,6 +1283,9 @@ void Core::setNospam(uint32_t nospam)
|
|||
emit idSet(getSelfId().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the unencrypted tox save data
|
||||
*/
|
||||
void Core::killTimers(bool onlyStop)
|
||||
{
|
||||
assert(QThread::currentThread() == coreThread);
|
||||
|
@ -1223,6 +1298,10 @@ void Core::killTimers(bool onlyStop)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Reinitialized the core.
|
||||
@warning Must be called from the Core thread, with the GUI thread ready to process events.
|
||||
*/
|
||||
void Core::reset()
|
||||
{
|
||||
assert(QThread::currentThread() == coreThread);
|
||||
|
|
|
@ -46,7 +46,7 @@ class Core : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit Core(QThread* coreThread, Profile& profile);
|
||||
static Core* getInstance(); ///< Returns the global widget's Core instance
|
||||
static Core* getInstance();
|
||||
CoreAV* getAv();
|
||||
~Core();
|
||||
|
||||
|
@ -59,41 +59,42 @@ public:
|
|||
|
||||
QString getPeerName(const ToxId& id) const;
|
||||
|
||||
QVector<uint32_t> getFriendList() const; ///< Returns the list of friendIds in our friendlist, an empty list on error
|
||||
int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure
|
||||
QString getGroupPeerName(int groupId, int peerId) const; ///< Get the name of a peer of a group
|
||||
ToxId getGroupPeerToxId(int groupId, int peerId) const; ///< Get the public key of a peer of a group
|
||||
QList<QString> getGroupPeerNames(int groupId) const; ///< Get the names of the peers of a group
|
||||
QString getFriendAddress(uint32_t friendNumber) const; ///< Get the full address if known, or public key of a friend
|
||||
QString getFriendPublicKey(uint32_t friendNumber) const; ///< Get the public key part of the ToxID only
|
||||
QString getFriendUsername(uint32_t friendNumber) const; ///< Get the username of a friend
|
||||
bool isFriendOnline(uint32_t friendId) const; ///< Check if a friend is online. Unknown friends are considered offline.
|
||||
bool hasFriendWithAddress(const QString &addr) const; ///< Check if we have a friend by address
|
||||
bool hasFriendWithPublicKey(const QString &pubkey) const; ///< Check if we have a friend by public key
|
||||
int joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const; ///< Accept a groupchat invite
|
||||
void quitGroupChat(int groupId) const; ///< Quit a groupchat
|
||||
QVector<uint32_t> getFriendList() const;
|
||||
int getGroupNumberPeers(int groupId) const;
|
||||
QString getGroupPeerName(int groupId, int peerId) const;
|
||||
ToxId getGroupPeerToxId(int groupId, int peerId) const;
|
||||
QList<QString> getGroupPeerNames(int groupId) const;
|
||||
QString getFriendAddress(uint32_t friendNumber) const;
|
||||
QString getFriendPublicKey(uint32_t friendNumber) const;
|
||||
QString getFriendUsername(uint32_t friendNumber) const;
|
||||
|
||||
QString getUsername() const; ///< Returns our username, or an empty string on failure
|
||||
Status getStatus() const; ///< Returns our user status
|
||||
QString getStatusMessage() const; ///< Returns our status message, or an empty string on failure
|
||||
ToxId getSelfId() const; ///< Returns our Tox ID
|
||||
QPair<QByteArray, QByteArray> getKeypair() const; ///< Returns our public and private keys
|
||||
bool isFriendOnline(uint32_t friendId) const;
|
||||
bool hasFriendWithAddress(const QString &addr) const;
|
||||
bool hasFriendWithPublicKey(const QString &pubkey) const;
|
||||
int joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const;
|
||||
void quitGroupChat(int groupId) const;
|
||||
|
||||
QString getUsername() const;
|
||||
Status getStatus() const;
|
||||
QString getStatusMessage() const;
|
||||
ToxId getSelfId() const;
|
||||
QPair<QByteArray, QByteArray> getKeypair() const;
|
||||
|
||||
static std::unique_ptr<TOX_PASS_KEY> createPasskey(const QString &password, uint8_t* salt = nullptr);
|
||||
static QByteArray encryptData(const QByteArray& data, const TOX_PASS_KEY& encryptionKey);
|
||||
static QByteArray encryptData(const QByteArray& data); ///< Uses the default profile's key
|
||||
static QByteArray encryptData(const QByteArray& data);
|
||||
static QByteArray decryptData(const QByteArray& data, const TOX_PASS_KEY &encryptionKey);
|
||||
static QByteArray decryptData(const QByteArray& data); ///< Uses the default profile's key
|
||||
static QByteArray decryptData(const QByteArray& data);
|
||||
|
||||
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
|
||||
bool isReady();
|
||||
|
||||
public slots:
|
||||
void start(); ///< Initializes the core, must be called before anything else
|
||||
void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events.
|
||||
void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
|
||||
void bootstrapDht(); ///< Connects us to the Tox network
|
||||
void start();
|
||||
void reset();
|
||||
void process();
|
||||
void bootstrapDht();
|
||||
|
||||
QByteArray getToxSaveData(); ///< Returns the unencrypted tox save data
|
||||
QByteArray getToxSaveData();
|
||||
|
||||
void acceptFriendRequest(const QString& userId);
|
||||
void requestFriendship(const QString& friendAddress, const QString& message);
|
||||
|
@ -225,7 +226,7 @@ private:
|
|||
void deadifyTox();
|
||||
|
||||
private slots:
|
||||
void killTimers(bool onlyStop); ///< Must only be called from the Core thread
|
||||
void killTimers(bool onlyStop);
|
||||
|
||||
private:
|
||||
Tox* tox;
|
||||
|
|
|
@ -31,7 +31,52 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
/**
|
||||
@fn void CoreAV::avInvite(uint32_t friendId, bool video)
|
||||
@brief Sent when a friend calls us.
|
||||
@param friendId Id of friend in call list.
|
||||
@param video False if chat is audio only, true audio and video.
|
||||
|
||||
@fn void CoreAV::avStart(uint32_t friendId, bool video)
|
||||
@brief Sent when a call we initiated has started.
|
||||
@param friendId Id of friend in call list.
|
||||
@param video False if chat is audio only, true audio and video.
|
||||
|
||||
@fn void CoreAV::avEnd(uint32_t friendId)
|
||||
@brief Sent when a call was ended by the peer.
|
||||
@param friendId Id of friend in call list.
|
||||
*/
|
||||
|
||||
/**
|
||||
@var CoreAV::AUDIO_DEFAULT_BITRATE
|
||||
@brief In kb/s. More than enough for Opus.
|
||||
|
||||
@var CoreAV::VIDEO_DEFAULT_BITRATE
|
||||
@brief Picked at random by fair dice roll.
|
||||
*/
|
||||
|
||||
/**
|
||||
@var std::atomic_flag CoreAV::threadSwitchLock
|
||||
@brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV thread.
|
||||
|
||||
The CoreAV thread must have priority for the flag, other threads should back off or release it quickly.
|
||||
CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
|
||||
toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
|
||||
most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
|
||||
and which we call via signals.
|
||||
When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
|
||||
when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
|
||||
switch to the UI thread to send it a signal. Both switches block both threads, so this would deadlock.
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Maps friend IDs to ToxFriendCall.
|
||||
*/
|
||||
IndexedList<ToxFriendCall> CoreAV::calls;
|
||||
|
||||
/**
|
||||
@brief Maps group IDs to ToxGroupCalls.
|
||||
*/
|
||||
IndexedList<ToxGroupCall> CoreAV::groupCalls;
|
||||
|
||||
using namespace std;
|
||||
|
@ -76,6 +121,9 @@ const ToxAV *CoreAV::getToxAv() const
|
|||
return toxav;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Starts the CoreAV main loop that calls toxav's main loop
|
||||
*/
|
||||
void CoreAV::start()
|
||||
{
|
||||
// Timers can only be touched from their own thread
|
||||
|
@ -84,6 +132,9 @@ void CoreAV::start()
|
|||
iterateTimer->start();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Stops the main loop
|
||||
*/
|
||||
void CoreAV::stop()
|
||||
{
|
||||
// Timers can only be touched from their own thread
|
||||
|
@ -92,6 +143,9 @@ void CoreAV::stop()
|
|||
iterateTimer->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Calls itself blocking queued on the coreav thread
|
||||
*/
|
||||
void CoreAV::killTimerFromThread()
|
||||
{
|
||||
// Timers can only be touched from their own thread
|
||||
|
@ -106,6 +160,11 @@ void CoreAV::process()
|
|||
iterateTimer->start(toxav_iteration_interval(toxav));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Check, that and calls are active now
|
||||
@return True is any calls are currently active, False otherwise
|
||||
@note A call about to start is not yet active
|
||||
*/
|
||||
bool CoreAV::anyActiveCalls()
|
||||
{
|
||||
return !calls.isEmpty();
|
||||
|
@ -234,6 +293,15 @@ void CoreAV::timeoutCall(uint32_t friendNum)
|
|||
emit avEnd(friendNum);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Send audio frame to a friend
|
||||
@param callId Id of friend in call list.
|
||||
@param pcm An array of audio samples (Pulse-code modulation).
|
||||
@param samples Number of samples in this frame.
|
||||
@param chans Number of audio channels.
|
||||
@param rate Audio sampling rate used in this frame.
|
||||
@return False only on error, but not if there's nothing to send.
|
||||
*/
|
||||
bool CoreAV::sendCallAudio(uint32_t callId, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate)
|
||||
{
|
||||
if (!calls.contains(callId))
|
||||
|
@ -378,6 +446,11 @@ void CoreAV::groupCallCallback(void* tox, int group, int peer,
|
|||
sample_rate);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get a call's video source.
|
||||
@param friendNum Id of friend in call list.
|
||||
@return Video surface to show
|
||||
*/
|
||||
VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
|
||||
{
|
||||
if (!calls.contains(friendNum))
|
||||
|
@ -389,6 +462,11 @@ VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
|
|||
return calls[friendNum].videoSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Starts a call in an existing AV groupchat.
|
||||
@note Call from the GUI thread.
|
||||
@param groupId Id of group to join
|
||||
*/
|
||||
void CoreAV::joinGroupCall(int groupId)
|
||||
{
|
||||
qDebug() << QString("Joining group call %1").arg(groupId);
|
||||
|
@ -397,6 +475,11 @@ void CoreAV::joinGroupCall(int groupId)
|
|||
call->inactive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Will not leave the group, just stop the call.
|
||||
@note Call from the GUI thread.
|
||||
@param groupId Id of group to leave
|
||||
*/
|
||||
void CoreAV::leaveGroupCall(int groupId)
|
||||
{
|
||||
qDebug() << QString("Leaving group call %1").arg(groupId);
|
||||
|
@ -450,11 +533,19 @@ bool CoreAV::isGroupCallVolEnabled(int groupId) const
|
|||
return !groupCalls[groupId].muteVol;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Check, that group has audio or video stream
|
||||
@param groupId Id of group to check
|
||||
@return True for AV groups, false for text-only groups
|
||||
*/
|
||||
bool CoreAV::isGroupAvEnabled(int groupId) const
|
||||
{
|
||||
return tox_group_get_type(Core::getInstance()->tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Forces to regenerate each call's audio sources.
|
||||
*/
|
||||
void CoreAV::invalidateCallSources()
|
||||
{
|
||||
for (ToxGroupCall& call : groupCalls)
|
||||
|
@ -468,6 +559,10 @@ void CoreAV::invalidateCallSources()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Signal to all peers that we're not sending video anymore.
|
||||
@note The next frame sent cancels this.
|
||||
*/
|
||||
void CoreAV::sendNoVideo()
|
||||
{
|
||||
// We don't change the audio bitrate, but we signal that we're not sending video anymore
|
||||
|
|
|
@ -46,26 +46,25 @@ public:
|
|||
|
||||
const ToxAV* getToxAv() const;
|
||||
|
||||
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
|
||||
bool anyActiveCalls();
|
||||
bool isCallVideoEnabled(uint32_t friendNum);
|
||||
/// Returns false only on error, but not if there's nothing to send
|
||||
bool sendCallAudio(uint32_t friendNum, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate);
|
||||
void sendCallVideo(uint32_t friendNum, std::shared_ptr<VideoFrame> frame);
|
||||
bool sendGroupCallAudio(int groupNum, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate);
|
||||
|
||||
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
|
||||
void invalidateCallSources(); ///< Forces to regenerate each call's audio sources
|
||||
void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this.
|
||||
VideoSource* getVideoSourceFromCall(int callNumber);
|
||||
void invalidateCallSources();
|
||||
void sendNoVideo();
|
||||
|
||||
void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
|
||||
void leaveGroupCall(int groupNum); ///< Will not leave the group, just stop the call. Call from the GUI thread.
|
||||
void joinGroupCall(int groupNum);
|
||||
void leaveGroupCall(int groupNum);
|
||||
void disableGroupCallMic(int groupNum);
|
||||
void disableGroupCallVol(int groupNum);
|
||||
void enableGroupCallMic(int groupNum);
|
||||
void enableGroupCallVol(int groupNum);
|
||||
bool isGroupCallMicEnabled(int groupNum) const;
|
||||
bool isGroupCallVolEnabled(int groupNum) const;
|
||||
bool isGroupAvEnabled(int groupNum) const; ///< True for AV groups, false for text-only groups
|
||||
bool isGroupAvEnabled(int groupNum) const;
|
||||
|
||||
void micMuteToggle(uint32_t friendNum);
|
||||
void volMuteToggle(uint32_t friendNum);
|
||||
|
@ -80,21 +79,19 @@ public slots:
|
|||
bool answerCall(uint32_t friendNum);
|
||||
bool cancelCall(uint32_t friendNum);
|
||||
void timeoutCall(uint32_t friendNum);
|
||||
/// Starts the CoreAV main loop that calls toxav's main loop
|
||||
void start();
|
||||
/// Stops the main loop
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void avInvite(uint32_t friendId, bool video); ///< Sent when a friend calls us
|
||||
void avStart(uint32_t friendId, bool video); ///< Sent when a call we initiated has started
|
||||
void avEnd(uint32_t friendId); ///< Sent when a call was ended by the peer
|
||||
void avInvite(uint32_t friendId, bool video);
|
||||
void avStart(uint32_t friendId, bool video);
|
||||
void avEnd(uint32_t friendId);
|
||||
|
||||
private slots:
|
||||
static void callCallback(ToxAV *toxAV, uint32_t friendNum, bool audio, bool video, void* self);
|
||||
static void stateCallback(ToxAV *, uint32_t friendNum, uint32_t state, void* self);
|
||||
static void bitrateCallback(ToxAV *toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* self);
|
||||
void killTimerFromThread(); ///< Calls itself blocking queued on the coreav thread
|
||||
void killTimerFromThread();
|
||||
|
||||
private:
|
||||
void process();
|
||||
|
@ -105,27 +102,15 @@ private:
|
|||
int32_t ystride, int32_t ustride, int32_t vstride, void* self);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64; ///< In kb/s. More than enough for Opus.
|
||||
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 6144; ///< Picked at random by fair dice roll.
|
||||
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64;
|
||||
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 6144;
|
||||
|
||||
private:
|
||||
ToxAV* toxav;
|
||||
std::unique_ptr<QThread> coreavThread;
|
||||
std::unique_ptr<QTimer> iterateTimer;
|
||||
static IndexedList<ToxFriendCall> calls;
|
||||
static IndexedList<ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
|
||||
/**
|
||||
* This flag is to be acquired before switching in a blocking way between the UI and CoreAV thread.
|
||||
* The CoreAV thread must have priority for the flag, other threads should back off or release it quickly.
|
||||
*
|
||||
* CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
|
||||
* toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
|
||||
* most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
|
||||
* and which we call via signals.
|
||||
* When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
|
||||
* when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
|
||||
* switch to the UI thread to send it a signal. Both switches block both threads, so this would deadlock.
|
||||
*/
|
||||
static IndexedList<ToxGroupCall> groupCalls;
|
||||
std::atomic_flag threadSwitchLock;
|
||||
|
||||
friend class Audio;
|
||||
|
|
|
@ -51,6 +51,12 @@ std::unique_ptr<TOX_PASS_KEY> Core::createPasskey(const QString& password, uint8
|
|||
return encryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Encrypts data.
|
||||
@note Uses the default profile's key.
|
||||
@param data Data to encrypt.
|
||||
@return Encrypted data.
|
||||
*/
|
||||
QByteArray Core::encryptData(const QByteArray &data)
|
||||
{
|
||||
return encryptData(data, Nexus::getProfile()->getPasskey());
|
||||
|
@ -68,6 +74,12 @@ QByteArray Core::encryptData(const QByteArray& data, const TOX_PASS_KEY& encrypt
|
|||
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Decrypts data.
|
||||
@note Uses the default profile's key.
|
||||
@param data Data to decrypt.
|
||||
@return Decrypted data.
|
||||
*/
|
||||
QByteArray Core::decryptData(const QByteArray &data)
|
||||
{
|
||||
return decryptData(data, Nexus::getProfile()->getPasskey());
|
||||
|
|
|
@ -30,14 +30,27 @@
|
|||
#include <QDir>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
@class CoreFile
|
||||
@brief Implements Core's file transfer callbacks.
|
||||
|
||||
Avoids polluting core.h with private internal callbacks.
|
||||
*/
|
||||
|
||||
QMutex CoreFile::fileSendMutex;
|
||||
QHash<uint64_t, ToxFile> CoreFile::fileMap;
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
@brief Get corefile iteration interval.
|
||||
|
||||
tox_iterate calls to get good file transfer performances
|
||||
@return The maximum amount of time in ms that Core should wait between two tox_iterate() calls.
|
||||
*/
|
||||
unsigned CoreFile::corefileIterationInterval()
|
||||
{
|
||||
/// Sleep at most 1000ms if we have no FT, 10 for user FTs, 50 for the rest (avatars, ...)
|
||||
constexpr unsigned fastFileInterval=10, slowFileInterval=50, idleInterval=1000;
|
||||
constexpr unsigned fastFileInterval = 10, slowFileInterval = 50, idleInterval = 1000;
|
||||
unsigned interval = idleInterval;
|
||||
|
||||
for (ToxFile& file : fileMap)
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
struct Tox;
|
||||
class Core;
|
||||
|
||||
/// Implements Core's file transfer callbacks
|
||||
/// Avoids polluting core.h with private internal callbacks
|
||||
class CoreFile
|
||||
{
|
||||
friend class Core;
|
||||
|
@ -57,8 +55,6 @@ private:
|
|||
static ToxFile *findFile(uint32_t friendId, uint32_t fileId);
|
||||
static void addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file);
|
||||
static void removeFile(uint32_t friendId, uint32_t fileId);
|
||||
/// Returns the maximum amount of time in ms that Core should wait between two
|
||||
/// tox_iterate calls to get good file transfer performances
|
||||
static unsigned corefileIterationInterval();
|
||||
|
||||
private:
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
|
||||
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
|
||||
|
||||
/**
|
||||
@file corestructs.h
|
||||
@brief Some headers use Core structs but don't need to include all of core.h
|
||||
|
||||
They should include this file directly instead to reduce compilation times
|
||||
*/
|
||||
|
||||
/**
|
||||
@var uint8_t ToxFile::fileKind
|
||||
@brief Data file (default) or avatar
|
||||
*/
|
||||
|
||||
ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QByteArray filename, QString filePath, FileDirection Direction)
|
||||
: fileKind{TOX_FILE_KIND_DATA}, fileNum(fileNum), friendId(friendId), fileName{filename},
|
||||
filePath{filePath}, file{new QFile(filePath)}, bytesSent{0}, filesize{0},
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
#ifndef CORESTRUCTS_H
|
||||
#define CORESTRUCTS_H
|
||||
|
||||
// Some headers use Core structs but don't need to include all of core.h
|
||||
// They should include this file directly instead to reduce compilation times
|
||||
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
class QFile;
|
||||
class QTimer;
|
||||
|
||||
|
@ -35,7 +33,7 @@ struct ToxFile
|
|||
RECEIVING
|
||||
};
|
||||
|
||||
ToxFile()=default;
|
||||
ToxFile() = default;
|
||||
ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, FileDirection Direction);
|
||||
~ToxFile(){}
|
||||
|
||||
|
@ -45,7 +43,7 @@ struct ToxFile
|
|||
void setFilePath(QString path);
|
||||
bool open(bool write);
|
||||
|
||||
uint8_t fileKind; ///< Data file (default) or avatar
|
||||
uint8_t fileKind;
|
||||
uint32_t fileNum;
|
||||
uint32_t friendId;
|
||||
QByteArray fileName;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
/// More or less a hashmap, indexed by the noexcept move-only value type's operator int
|
||||
/// Really nice to store our ToxCall objects.
|
||||
template <typename T>
|
||||
class IndexedList
|
||||
{
|
||||
|
@ -13,30 +11,72 @@ public:
|
|||
explicit IndexedList() = default;
|
||||
|
||||
// Qt
|
||||
inline bool isEmpty() { return v.empty(); }
|
||||
bool contains(int i) { return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end(); }
|
||||
void remove(int i) { v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end()); }
|
||||
T& operator[](int i)
|
||||
bool isEmpty()
|
||||
{
|
||||
return v.empty();
|
||||
}
|
||||
|
||||
bool contains(int i)
|
||||
{
|
||||
return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end();
|
||||
}
|
||||
|
||||
void remove(int i)
|
||||
{
|
||||
v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end());
|
||||
}
|
||||
|
||||
T &operator[](int i)
|
||||
{
|
||||
iterator it = std::find_if(begin(), end(), [i](T& t){return (int)t == i;});
|
||||
if (it == end())
|
||||
it = insert({});
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
|
||||
// STL
|
||||
using iterator = typename std::vector<T>::iterator;
|
||||
using const_iterator = typename std::vector<T>::const_iterator;
|
||||
|
||||
inline iterator begin() { return v.begin(); }
|
||||
inline const_iterator begin() const { return v.begin(); }
|
||||
inline const_iterator cbegin() const { return v.cbegin(); }
|
||||
inline iterator end() { return v.end(); }
|
||||
inline const_iterator end() const { return v.end(); }
|
||||
inline const_iterator cend() const { return v.cend(); }
|
||||
inline iterator erase(iterator pos) { return v.erase(pos); }
|
||||
inline iterator erase(iterator first, iterator last) { return v.erase(first, last); }
|
||||
inline iterator insert(T&& value) { v.push_back(std::move(value)); return --v.end(); }
|
||||
inline iterator begin()
|
||||
{
|
||||
return v.begin();
|
||||
}
|
||||
inline const_iterator begin() const
|
||||
{
|
||||
return v.begin();
|
||||
}
|
||||
inline const_iterator cbegin() const
|
||||
{
|
||||
return v.cbegin();
|
||||
}
|
||||
inline iterator end()
|
||||
{
|
||||
return v.end();
|
||||
}
|
||||
inline const_iterator end() const
|
||||
{
|
||||
return v.end();
|
||||
}
|
||||
inline const_iterator cend() const
|
||||
{
|
||||
return v.cend();
|
||||
}
|
||||
inline iterator erase(iterator pos)
|
||||
{
|
||||
return v.erase(pos);
|
||||
}
|
||||
inline iterator erase(iterator first, iterator last)
|
||||
{
|
||||
return v.erase(first, last);
|
||||
}
|
||||
inline iterator insert(T&& value)
|
||||
{
|
||||
v.push_back(std::move(value));
|
||||
return --v.end();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> v;
|
||||
|
|
|
@ -7,6 +7,23 @@
|
|||
#include <QTimer>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
/**
|
||||
@var uint32_t ToxCall::callId
|
||||
@brief Could be a friendNum or groupNum, must uniquely identify the call. Do not modify!
|
||||
|
||||
@var bool ToxCall::inactive
|
||||
@brief True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
|
||||
|
||||
@var bool ToxCall::videoEnabled
|
||||
@brief True if our user asked for a video call, sending and recieving.
|
||||
|
||||
@var bool ToxCall::nullVideoBitrate
|
||||
@brief True if our video bitrate is zero, i.e. if the device is closed.
|
||||
|
||||
@var TOXAV_FRIEND_CALL_STATE ToxCall::state
|
||||
@brief State of the peer (not ours!)
|
||||
*/
|
||||
|
||||
using namespace std;
|
||||
|
||||
ToxCall::ToxCall(uint32_t CallId)
|
||||
|
|
|
@ -32,9 +32,9 @@ protected:
|
|||
QMetaObject::Connection audioInConn;
|
||||
|
||||
public:
|
||||
uint32_t callId; ///< Could be a friendNum or groupNum, must uniquely identify the call. Do not modify!
|
||||
uint32_t callId;
|
||||
quint32 alSource;
|
||||
bool inactive; ///< True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
|
||||
bool inactive;
|
||||
bool muteMic;
|
||||
bool muteVol;
|
||||
};
|
||||
|
@ -48,10 +48,10 @@ struct ToxFriendCall : public ToxCall
|
|||
|
||||
ToxFriendCall& operator=(ToxFriendCall&& other) noexcept;
|
||||
|
||||
bool videoEnabled; ///< True if our user asked for a video call, sending and recving
|
||||
bool nullVideoBitrate; ///< True if our video bitrate is zero, i.e. if the device is closed
|
||||
bool videoEnabled;
|
||||
bool nullVideoBitrate;
|
||||
CoreVideoSource* videoSource;
|
||||
TOXAV_FRIEND_CALL_STATE state; ///< State of the peer (not ours!)
|
||||
TOXAV_FRIEND_CALL_STATE state;
|
||||
|
||||
void startTimeout();
|
||||
void stopTimeout();
|
||||
|
|
|
@ -30,14 +30,46 @@
|
|||
#define TOX_ID_CHECKSUM_LENGTH 4
|
||||
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
|
||||
|
||||
/**
|
||||
@class ToxId
|
||||
@brief This class represents a Tox ID.
|
||||
|
||||
An ID is composed of 32 bytes long public key, 4 bytes long NoSpam
|
||||
and 2 bytes long checksum.
|
||||
|
||||
e.g.
|
||||
@code
|
||||
| C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30 | C8BA3AB9 | BEB9
|
||||
| / |
|
||||
| / NoSpam | Checksum
|
||||
| Public Key (PK), 32 bytes, 64 characters / 4 bytes | 2 bytes
|
||||
| | 8 characters| 4 characters
|
||||
@endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief The default constructor. Creates an empty Tox ID.
|
||||
*/
|
||||
ToxId::ToxId()
|
||||
: publicKey(), noSpam(), checkSum()
|
||||
{}
|
||||
|
||||
/**
|
||||
@brief The copy constructor.
|
||||
@param other ToxId to copy
|
||||
*/
|
||||
ToxId::ToxId(const ToxId &other)
|
||||
: publicKey(other.publicKey), noSpam(other.noSpam), checkSum(other.checkSum)
|
||||
{}
|
||||
|
||||
/**
|
||||
@brief Create a Tox ID from QString.
|
||||
|
||||
If the given id is not a valid Tox ID, then:
|
||||
publicKey == id and noSpam == "" == checkSum.
|
||||
|
||||
@param id Tox ID string to convert to ToxId object
|
||||
*/
|
||||
ToxId::ToxId(const QString &id)
|
||||
{
|
||||
if (isToxId(id))
|
||||
|
@ -52,26 +84,48 @@ ToxId::ToxId(const QString &id)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Compares, that public key equals.
|
||||
@param other Tox ID to compare.
|
||||
@return True if both Tox ID have same public keys, false otherwise.
|
||||
*/
|
||||
bool ToxId::operator==(const ToxId& other) const
|
||||
{
|
||||
return publicKey == other.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Compares, that only public key not equals.
|
||||
@param other Tox ID to compare.
|
||||
@return True if both Tox ID have different public keys, false otherwise.
|
||||
*/
|
||||
bool ToxId::operator!=(const ToxId &other) const
|
||||
{
|
||||
return publicKey != other.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Check, that the current user ID is the active user ID
|
||||
@return True if this Tox ID is equals to
|
||||
the Tox ID of the currently active profile.
|
||||
*/
|
||||
bool ToxId::isSelf() const
|
||||
{
|
||||
return *this == Core::getInstance()->getSelfId();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns Tox ID converted to QString.
|
||||
@return The Tox ID as QString.
|
||||
*/
|
||||
QString ToxId::toString() const
|
||||
{
|
||||
return publicKey + noSpam + checkSum;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Clears all elements of the Tox ID.
|
||||
*/
|
||||
void ToxId::clear()
|
||||
{
|
||||
publicKey.clear();
|
||||
|
@ -79,6 +133,11 @@ void ToxId::clear()
|
|||
checkSum.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Check, that id is a valid Tox ID.
|
||||
@param id Tox ID to check.
|
||||
@return True if id is a valid Tox ID, false otherwise.
|
||||
*/
|
||||
bool ToxId::isToxId(const QString &id)
|
||||
{
|
||||
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");
|
||||
|
|
|
@ -23,36 +23,20 @@
|
|||
|
||||
#include <QString>
|
||||
|
||||
/*
|
||||
* This class represents a Tox ID.
|
||||
* An ID is composed of 32 bytes long public key, 4 bytes long NoSpam
|
||||
* and 2 bytes long checksum.
|
||||
*
|
||||
* e.g.
|
||||
*
|
||||
* | C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30 | C8BA3AB9 | BEB9
|
||||
* | / |
|
||||
* | / NoSpam | Checksum
|
||||
* | Public Key (PK), 32 bytes, 64 characters / 4 bytes | 2 bytes
|
||||
* | | 8 characters| 4 characters
|
||||
*/
|
||||
class ToxId
|
||||
{
|
||||
public:
|
||||
ToxId(); ///< The default constructor. Creates an empty Tox ID.
|
||||
ToxId(const ToxId& other); ///< The copy constructor.
|
||||
explicit ToxId(const QString& id); ///< Create a Tox ID from QString.
|
||||
/// If the given id is not a valid Tox ID, then:
|
||||
/// publicKey == id and noSpam == "" == checkSum.
|
||||
ToxId();
|
||||
ToxId(const ToxId& other);
|
||||
explicit ToxId(const QString& id);
|
||||
|
||||
bool operator==(const ToxId& other) const; ///< Compares only publicKey.
|
||||
bool operator!=(const ToxId& other) const; ///< Compares only publicKey.
|
||||
bool isSelf() const; ///< Returns true if this Tox ID is equals to
|
||||
/// the Tox ID of the currently active profile.
|
||||
QString toString() const; ///< Returns the Tox ID as QString.
|
||||
void clear(); ///< Clears all elements of the Tox ID.
|
||||
bool operator==(const ToxId& other) const;
|
||||
bool operator!=(const ToxId& other) const;
|
||||
bool isSelf() const;
|
||||
QString toString() const;
|
||||
void clear();
|
||||
|
||||
static bool isToxId(const QString& id); ///< Returns true if id is a valid Tox ID.
|
||||
static bool isToxId(const QString& id);
|
||||
|
||||
public:
|
||||
QString publicKey;
|
||||
|
|
|
@ -51,6 +51,9 @@ Friend::~Friend()
|
|||
delete widget;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Loads the friend's chat history if enabled
|
||||
*/
|
||||
void Friend::loadHistory()
|
||||
{
|
||||
if (Nexus::getProfile()->isHistoryEnabled())
|
||||
|
|
|
@ -37,7 +37,6 @@ public:
|
|||
~Friend();
|
||||
Friend& operator=(const Friend& other)=delete;
|
||||
|
||||
/// Loads the friend's chat history if enabled
|
||||
void loadHistory();
|
||||
|
||||
void setName(QString name);
|
||||
|
|
57
src/ipc.cpp
57
src/ipc.cpp
|
@ -25,6 +25,19 @@
|
|||
#include <random>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
@var time_t IPC::lastEvent
|
||||
@brief When last event was posted.
|
||||
|
||||
@var time_t IPC::lastProcessed
|
||||
@brief When processEvents() ran last time
|
||||
*/
|
||||
|
||||
/**
|
||||
@class IPC
|
||||
@brief Inter-process communication
|
||||
*/
|
||||
|
||||
IPC::IPC()
|
||||
: globalMemory{"qtox-" IPC_PROTOCOL_VERSION}
|
||||
{
|
||||
|
@ -84,13 +97,23 @@ IPC::~IPC()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
IPC& IPC::getInstance()
|
||||
{
|
||||
static IPC instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
time_t IPC::postEvent(const QString &name, const QByteArray& data/*=QByteArray()*/, uint32_t dest/*=0*/)
|
||||
/**
|
||||
@brief Post IPC event.
|
||||
@param name Name to set in IPC event.
|
||||
@param data Data to set in IPC event (default QByteArray()).
|
||||
@param dest Settings::getCurrentProfileId() or 0 (main instance, default).
|
||||
@return Time the event finished.
|
||||
*/
|
||||
time_t IPC::postEvent(const QString &name, const QByteArray& data, uint32_t dest)
|
||||
{
|
||||
QByteArray binName = name.toUtf8();
|
||||
if (binName.length() > (int32_t)sizeof(IPCEvent::name))
|
||||
|
@ -101,7 +124,7 @@ time_t IPC::postEvent(const QString &name, const QByteArray& data/*=QByteArray()
|
|||
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
IPCEvent* evt = 0;
|
||||
IPCEvent* evt = nullptr;
|
||||
IPCMemory* mem = global();
|
||||
time_t result = 0;
|
||||
|
||||
|
@ -195,6 +218,10 @@ bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout/*=-1*/)
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Only called when global memory IS LOCKED.
|
||||
@return nullptr if no evnts present, IPC event otherwise
|
||||
*/
|
||||
IPC::IPCEvent *IPC::fetchEvent()
|
||||
{
|
||||
IPCMemory* mem = global();
|
||||
|
@ -209,32 +236,28 @@ IPC::IPCEvent *IPC::fetchEvent()
|
|||
(!evt->processed && difftime(time(0), evt->posted) > EVENT_GC_TIMEOUT))
|
||||
memset(evt, 0, sizeof(IPCEvent));
|
||||
|
||||
if (evt->posted && !evt->processed && evt->sender != getpid())
|
||||
{
|
||||
if (evt->dest == Settings::getInstance().getCurrentProfileId() || (evt->dest == 0 && isCurrentOwner()))
|
||||
return evt;
|
||||
}
|
||||
if (evt->posted && !evt->processed && evt->sender != getpid()
|
||||
&& (evt->dest == Settings::getInstance().getCurrentProfileId()
|
||||
|| (evt->dest == 0 && isCurrentOwner())))
|
||||
return evt;
|
||||
}
|
||||
return 0;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
||||
{
|
||||
bool result = false;
|
||||
if (QThread::currentThread() != qApp->thread())
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
result = handler(arg);
|
||||
else
|
||||
QMetaObject::invokeMethod(this, "runEventHandler",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(IPCEventHandler, handler),
|
||||
Q_ARG(const QByteArray&, arg));
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = handler(arg);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void IPC::processEvents()
|
||||
|
|
|
@ -64,14 +64,11 @@ public:
|
|||
struct IPCMemory
|
||||
{
|
||||
uint64_t globalId;
|
||||
// When last event was posted
|
||||
time_t lastEvent;
|
||||
// When processEvents() ran last time
|
||||
time_t lastProcessed;
|
||||
IPCEvent events[IPC::EVENT_QUEUE_SIZE];
|
||||
};
|
||||
|
||||
// dest: Settings::getCurrentProfileId() or 0 (main instance).
|
||||
time_t postEvent(const QString& name, const QByteArray &data=QByteArray(), uint32_t dest=0);
|
||||
bool isCurrentOwner();
|
||||
void registerEventHandler(const QString& name, IPCEventHandler handler);
|
||||
|
@ -84,7 +81,6 @@ protected slots:
|
|||
protected:
|
||||
IPCMemory* global();
|
||||
bool runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
||||
// Only called when global memory IS LOCKED, returns 0 if no evnts present
|
||||
IPCEvent* fetchEvent();
|
||||
|
||||
QTimer timer;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "src/net/autoupdate.h"
|
||||
#include "src/persistence/serialize.h"
|
||||
#include "src/persistence/settings.h"
|
||||
|
@ -40,6 +39,13 @@
|
|||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
@file autoupdate.cpp
|
||||
|
||||
For now we only support auto updates on Windows and OS X, although extending it is not a technical issue.
|
||||
Linux users are expected to use their package managers or update manually through official channels.
|
||||
*/
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN64
|
||||
const QString AutoUpdater::platform = "win64";
|
||||
|
@ -72,6 +78,50 @@ const QString AutoUpdater::updaterBin;
|
|||
const QString AutoUpdater::updateServer;
|
||||
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES];
|
||||
#endif
|
||||
|
||||
/**
|
||||
@var unsigned char AutoUpdater::UpdateFileMeta::sig[crypto_sign_BYTES]
|
||||
@brief Signature of the file (ed25519)
|
||||
|
||||
@var QString AutoUpdater::UpdateFileMeta::id
|
||||
@brief Unique id of the file
|
||||
|
||||
@var QString AutoUpdater::UpdateFileMeta::installpath
|
||||
@brief Local path including the file name. May be relative to qtox-updater or absolute
|
||||
|
||||
@var uint64_t AutoUpdater::UpdateFileMeta::size
|
||||
@brief Size in bytes of the file
|
||||
*/
|
||||
|
||||
/**
|
||||
@var static const QString AutoUpdater::updateServer
|
||||
@brief Hostname of the qTox update server
|
||||
|
||||
@var static const QString AutoUpdater::platform
|
||||
@brief Name of platform we're trying to get updates for
|
||||
|
||||
@var static const QString AutoUpdater::checkURI
|
||||
@brief URI of the file containing the latest version string
|
||||
|
||||
@var static const QString AutoUpdater::flistURI
|
||||
@brief URI of the file containing info on each file (hash, signature, size, name, ..)
|
||||
|
||||
@var static const QString AutoUpdater::filesURI
|
||||
@brief URI of the actual files of the latest version
|
||||
|
||||
@var static const QString AutoUpdater::updaterBin
|
||||
@brief Path to the qtox-updater binary
|
||||
|
||||
@var static std::atomic_bool AutoUpdater::abortFlag
|
||||
@brief If true, try to abort everything.
|
||||
|
||||
@var static std::atomic_bool AutoUpdater::isDownloadingUpdate
|
||||
@brief We'll pretend there's no new update available if we're already updating
|
||||
|
||||
@var static QMutex AutoUpdater::progressVersionMutex
|
||||
@brief No, we can't just make the QString atomic
|
||||
*/
|
||||
|
||||
const QString AutoUpdater::checkURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/version";
|
||||
const QString AutoUpdater::flistURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/flist";
|
||||
const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/files/";
|
||||
|
@ -81,6 +131,19 @@ std::atomic<float> AutoUpdater::progressValue{0};
|
|||
QString AutoUpdater::progressVersion;
|
||||
QMutex AutoUpdater::progressVersionMutex;
|
||||
|
||||
/**
|
||||
@class AutoUpdater
|
||||
@brief Handles checking and applying updates for qTox.
|
||||
@note Do *NOT* use auto update unless AUTOUPDATE_ENABLED is defined to 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Checks if an update is available for download.
|
||||
@return True if an update is available for download, false otherwise.
|
||||
|
||||
Connects to the qTox update server, and check if an update is available for download
|
||||
Will call getUpdateVersion, and as such may block and processEvents.
|
||||
*/
|
||||
bool AutoUpdater::isUpdateAvailable()
|
||||
{
|
||||
if (isDownloadingUpdate)
|
||||
|
@ -95,6 +158,11 @@ bool AutoUpdater::isUpdateAvailable()
|
|||
return !diff.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Fetch the version info of the last update available from the qTox update server
|
||||
@note Will try to follow qTox's proxy settings, may block and processEvents
|
||||
@return Avaliable version info.
|
||||
*/
|
||||
AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
|
||||
{
|
||||
VersionInfo versionInfo;
|
||||
|
@ -159,6 +227,11 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
|
|||
return versionInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Parses and validates a flist file.
|
||||
@param flistData Install file data.
|
||||
@return An empty list on error.
|
||||
*/
|
||||
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
|
||||
{
|
||||
QList<UpdateFileMeta> flist;
|
||||
|
@ -218,6 +291,11 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
|
|||
return flist;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Gets the update server's flist.
|
||||
@note Will try to follow qTox's proxy settings, may block and processEvents
|
||||
@return An empty array on error
|
||||
*/
|
||||
QByteArray AutoUpdater::getUpdateFlist()
|
||||
{
|
||||
QByteArray flist;
|
||||
|
@ -247,6 +325,11 @@ QByteArray AutoUpdater::getUpdateFlist()
|
|||
return flist;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Generates a list of files we need to update.
|
||||
@param updateFlist List of files available to update.
|
||||
@return List of files we need to update.
|
||||
*/
|
||||
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMeta> updateFlist)
|
||||
{
|
||||
QList<UpdateFileMeta> diff;
|
||||
|
@ -258,6 +341,11 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMe
|
|||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if we have an up to date version of this file locally installed.
|
||||
@param fileMeta File to check.
|
||||
@return True if file doesn't need updates, false if need.
|
||||
*/
|
||||
bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
|
||||
{
|
||||
QString appDir = qApp->applicationDirPath();
|
||||
|
@ -273,6 +361,14 @@ bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Tries to fetch the file from the update server.
|
||||
@note Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
|
||||
@note Will try to follow qTox's proxy settings, may block and processEvents.
|
||||
@param fileMeta Meta data fo file to update.
|
||||
@param progressCallback Callback function, which will connected with QNetworkReply::downloadProgress
|
||||
@return A file with a null QByteArray on error.
|
||||
*/
|
||||
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
|
||||
std::function<void(int,int)> progressCallback)
|
||||
{
|
||||
|
@ -305,7 +401,11 @@ AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
|
|||
return file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief Will try to download an update, if successful qTox will apply it after a restart
|
||||
@note Will try to follow qTox's proxy settings, may block and processEvents
|
||||
@result True if successful and qTox will apply it after a restart
|
||||
*/
|
||||
bool AutoUpdater::downloadUpdate()
|
||||
{
|
||||
// Updates only for supported platforms
|
||||
|
@ -433,6 +533,13 @@ fail:
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if an update is downloaded and ready to be installed.
|
||||
@note If result is true, call installLocalUpdate,
|
||||
@return True if an update is downloaded, false if partially downloaded.
|
||||
|
||||
If an update was partially downloaded, the function will resume asynchronously and return false.
|
||||
*/
|
||||
bool AutoUpdater::isLocalUpdateReady()
|
||||
{
|
||||
// Updates only for supported platforms
|
||||
|
@ -472,6 +579,13 @@ bool AutoUpdater::isLocalUpdateReady()
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Launches the qTox updater to try to install the local update and exits immediately.
|
||||
|
||||
@note Will not check that the update actually exists, use isLocalUpdateReady first for that.
|
||||
The qTox updater will restart us after the update is done.
|
||||
If we fail to start the qTox updater, we will delete the update and exit.
|
||||
*/
|
||||
void AutoUpdater::installLocalUpdate()
|
||||
{
|
||||
qDebug() << "About to start the qTox updater to install a local update";
|
||||
|
@ -511,6 +625,14 @@ void AutoUpdater::installLocalUpdate()
|
|||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks update an show dialog asking to download it.
|
||||
@note Runs asynchronously in its own thread, and will return immediatly
|
||||
|
||||
Will call isUpdateAvailable, and as such may processEvents.
|
||||
Connects to the qTox update server, if an update is found
|
||||
shows a dialog to the user asking to download it.
|
||||
*/
|
||||
void AutoUpdater::checkUpdatesAsyncInteractive()
|
||||
{
|
||||
if (isDownloadingUpdate)
|
||||
|
@ -519,6 +641,11 @@ void AutoUpdater::checkUpdatesAsyncInteractive()
|
|||
QtConcurrent::run(&AutoUpdater::checkUpdatesAsyncInteractiveWorker);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Does the actual work for checkUpdatesAsyncInteractive
|
||||
|
||||
Blocking, but otherwise has the same properties than checkUpdatesAsyncInteractive
|
||||
*/
|
||||
void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
|
||||
{
|
||||
if (!isUpdateAvailable())
|
||||
|
@ -557,18 +684,32 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Thread safe setter
|
||||
@param version Version to set.
|
||||
*/
|
||||
void AutoUpdater::setProgressVersion(QString version)
|
||||
{
|
||||
QMutexLocker lock(&progressVersionMutex);
|
||||
progressVersion = version;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Abort update process.
|
||||
|
||||
@note Aborting will make some functions try to return early.
|
||||
Call before qTox exits to avoid the updater running in the background.
|
||||
*/
|
||||
void AutoUpdater::abortUpdates()
|
||||
{
|
||||
abortFlag = true;
|
||||
isDownloadingUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Functions giving info on the progress of update downloads.
|
||||
@return Version as string.
|
||||
*/
|
||||
QString AutoUpdater::getProgressVersion()
|
||||
{
|
||||
QMutexLocker lock(&progressVersionMutex);
|
||||
|
|
|
@ -28,8 +28,6 @@
|
|||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
/// For now we only support auto updates on Windows and OS X, although extending it is not a technical issue.
|
||||
/// Linux users are expected to use their package managers or update manually through official channels.
|
||||
#ifdef Q_OS_WIN
|
||||
#define AUTOUPDATE_ENABLED 1
|
||||
#elif defined(Q_OS_OSX)
|
||||
|
@ -38,17 +36,15 @@
|
|||
#define AUTOUPDATE_ENABLED 0
|
||||
#endif
|
||||
|
||||
/// Handles checking and applying updates for qTox
|
||||
/// Do *NOT* use auto update unless AUTOUPDATE_ENABLED is defined to 1
|
||||
class AutoUpdater
|
||||
{
|
||||
public:
|
||||
struct UpdateFileMeta
|
||||
{
|
||||
unsigned char sig[crypto_sign_BYTES]; ///< Signature of the file (ed25519)
|
||||
QString id; ///< Unique id of the file
|
||||
QString installpath; ///< Local path including the file name. May be relative to qtox-updater or absolute
|
||||
uint64_t size; ///< Size in bytes of the file
|
||||
unsigned char sig[crypto_sign_BYTES];
|
||||
QString id;
|
||||
QString installpath;
|
||||
uint64_t size;
|
||||
|
||||
bool operator==(const UpdateFileMeta& other)
|
||||
{
|
||||
|
@ -71,72 +67,41 @@ public:
|
|||
};
|
||||
|
||||
public:
|
||||
/// Connects to the qTox update server, if an updat is found shows a dialog to the user asking to download it
|
||||
/// Runs asynchronously in its own thread, and will return immediatly
|
||||
/// Will call isUpdateAvailable, and as such may processEvents
|
||||
static void checkUpdatesAsyncInteractive();
|
||||
/// Connects to the qTox update server, returns true if an update is available for download
|
||||
/// Will call getUpdateVersion, and as such may block and processEvents
|
||||
static bool isUpdateAvailable();
|
||||
/// Fetch the version info of the last update available from the qTox update server
|
||||
/// Will try to follow qTox's proxy settings, may block and processEvents
|
||||
static VersionInfo getUpdateVersion();
|
||||
/// Will try to download an update, if successful returns true and qTox will apply it after a restart
|
||||
/// Will try to follow qTox's proxy settings, may block and processEvents
|
||||
static bool downloadUpdate();
|
||||
/// Returns true if an update is downloaded and ready to be installed,
|
||||
/// if so, call installLocalUpdate.
|
||||
/// If an update was partially downloaded, the function will resume asynchronously and return false
|
||||
static bool isLocalUpdateReady();
|
||||
/// Launches the qTox updater to try to install the local update and exits immediately
|
||||
/// Will not check that the update actually exists, use isLocalUpdateReady first for that
|
||||
/// The qTox updater will restart us after the update is done
|
||||
/// Note: If we fail to start the qTox updater, we will delete the update and exit
|
||||
[[ noreturn ]] static void installLocalUpdate();
|
||||
/// Aborting will make some functions try to return early
|
||||
/// Call before qTox exits to avoid the updater running in the background
|
||||
static void abortUpdates();
|
||||
/// Functions giving info on the progress of update downloads
|
||||
static QString getProgressVersion();
|
||||
static int getProgressValue();
|
||||
|
||||
protected:
|
||||
/// Parses and validates a flist file. Returns an empty list on error
|
||||
static QList<UpdateFileMeta> parseFlist(QByteArray flistData);
|
||||
/// Gets the update server's flist. Returns an empty array on error
|
||||
/// Will try to follow qTox's proxy settings, may block and processEvents
|
||||
static QByteArray getUpdateFlist();
|
||||
/// Generates a list of files we need to update
|
||||
static QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
|
||||
/// Checks if we have an up to date version of this file locally installed
|
||||
static bool isUpToDate(UpdateFileMeta file);
|
||||
/// Tries to fetch the file from the update server. Returns a file with a null QByteArray on error.
|
||||
/// Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
|
||||
/// Will try to follow qTox's proxy settings, may block and processEvents
|
||||
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta, std::function<void(int,int)> progressCallback);
|
||||
/// Does the actual work for checkUpdatesAsyncInteractive
|
||||
/// Blocking, but otherwise has the same properties than checkUpdatesAsyncInteractive
|
||||
static void checkUpdatesAsyncInteractiveWorker();
|
||||
/// Thread safe setter
|
||||
static void setProgressVersion(QString version);
|
||||
|
||||
private:
|
||||
AutoUpdater() = delete;
|
||||
|
||||
private:
|
||||
// Constants
|
||||
static const QString updateServer; ///< Hostname of the qTox update server
|
||||
static const QString platform; ///< Name of platform we're trying to get updates for
|
||||
static const QString checkURI; ///< URI of the file containing the latest version string
|
||||
static const QString flistURI; ///< URI of the file containing info on each file (hash, signature, size, name, ..)
|
||||
static const QString filesURI; ///< URI of the actual files of the latest version
|
||||
static const QString updaterBin; ///< Path to the qtox-updater binary
|
||||
static const QString updateServer;
|
||||
static const QString platform;
|
||||
static const QString checkURI;
|
||||
static const QString flistURI;
|
||||
static const QString filesURI;
|
||||
static const QString updaterBin;
|
||||
static unsigned char key[];
|
||||
static std::atomic_bool abortFlag; ///< If true, try to abort everything.
|
||||
static std::atomic_bool isDownloadingUpdate; ///< We'll pretend there's no new update available if we're already updating
|
||||
static std::atomic_bool abortFlag;
|
||||
static std::atomic_bool isDownloadingUpdate;
|
||||
static std::atomic<float> progressValue;
|
||||
static QString progressVersion;
|
||||
static QMutex progressVersionMutex; ///< No, we can't just make the QString atomic
|
||||
static QMutex progressVersionMutex;
|
||||
};
|
||||
|
||||
#endif // AUTOUPDATE_H
|
||||
|
|
|
@ -23,6 +23,14 @@
|
|||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
@class AvatarBroadcaster
|
||||
|
||||
Takes care of broadcasting avatar changes to our friends in a smart way
|
||||
Cache a copy of our current avatar and friends who have received it
|
||||
so we don't spam avatar transfers to a friend who already has it.
|
||||
*/
|
||||
|
||||
QByteArray AvatarBroadcaster::avatarData;
|
||||
QMap<uint32_t, bool> AvatarBroadcaster::friendsSentTo;
|
||||
|
||||
|
@ -32,10 +40,15 @@ static auto autoBroadcast = [](uint32_t friendId, Status)
|
|||
AvatarBroadcaster::sendAvatarTo(friendId);
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Set our current avatar.
|
||||
@param data Byte array on avater.
|
||||
*/
|
||||
void AvatarBroadcaster::setAvatar(QByteArray data)
|
||||
{
|
||||
if (avatarData == data)
|
||||
return;
|
||||
|
||||
avatarData = data;
|
||||
friendsSentTo.clear();
|
||||
|
||||
|
@ -44,6 +57,10 @@ void AvatarBroadcaster::setAvatar(QByteArray data)
|
|||
sendAvatarTo(friendId);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Send our current avatar to this friend, if not already sent
|
||||
@param friendId Id of friend to send avatar.
|
||||
*/
|
||||
void AvatarBroadcaster::sendAvatarTo(uint32_t friendId)
|
||||
{
|
||||
if (friendsSentTo.contains(friendId) && friendsSentTo[friendId])
|
||||
|
@ -54,6 +71,10 @@ void AvatarBroadcaster::sendAvatarTo(uint32_t friendId)
|
|||
friendsSentTo[friendId] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Setup auto broadcast sending avatar.
|
||||
@param state If true, we automatically broadcast our avatar to friends when they come online.
|
||||
*/
|
||||
void AvatarBroadcaster::enableAutoBroadcast(bool state)
|
||||
{
|
||||
QObject::disconnect(autoBroadcastConn);
|
||||
|
|
|
@ -24,20 +24,14 @@
|
|||
#include <QByteArray>
|
||||
#include <QMap>
|
||||
|
||||
/// Takes care of broadcasting avatar changes to our friends in a smart way
|
||||
/// Cache a copy of our current avatar and friends who have received it
|
||||
/// so we don't spam avatar transfers to a friend who already has it.
|
||||
class AvatarBroadcaster
|
||||
{
|
||||
private:
|
||||
AvatarBroadcaster()=delete;
|
||||
|
||||
public:
|
||||
/// Set our current avatar
|
||||
static void setAvatar(QByteArray data);
|
||||
/// Send our current avatar to this friend, if not already sent
|
||||
static void sendAvatarTo(uint32_t friendId);
|
||||
/// If true, we automatically broadcast our avatar to friends when they come online
|
||||
static void enableAutoBroadcast(bool state = true);
|
||||
|
||||
private:
|
||||
|
|
|
@ -29,6 +29,22 @@
|
|||
|
||||
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
|
||||
|
||||
/**
|
||||
@class ToxDNS
|
||||
@brief Handles tox1 and tox3 DNS queries.
|
||||
*/
|
||||
|
||||
/**
|
||||
@struct tox3_server
|
||||
@brief Represents a tox3 server.
|
||||
|
||||
@var const char* tox3_server::name
|
||||
@brief Hostname of the server, e.g. toxme.se.
|
||||
|
||||
@var uint8_t* tox3_server::pubkey
|
||||
@brief Public key of the tox3 server, usually 256bit long.
|
||||
*/
|
||||
|
||||
const ToxDNS::tox3_server ToxDNS::pinnedServers[]
|
||||
{
|
||||
{"toxme.se", (uint8_t[32]){0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14,
|
||||
|
@ -50,10 +66,14 @@ void ToxDNS::showWarning(const QString &message)
|
|||
warning.exec();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Try to fetch the first entry of the given TXT record.
|
||||
@param record Record to search.
|
||||
@param silent May display message boxes on error if silent is false.
|
||||
@return An empty object on failure. May block for up to ~3s.
|
||||
*/
|
||||
QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
QDnsLookup dns;
|
||||
dns.setType(QDnsLookup::TXT);
|
||||
dns.setName(record);
|
||||
|
@ -71,7 +91,7 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
|||
if (!silent)
|
||||
showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||
|
||||
return result;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (dns.error() == QDnsLookup::NotFoundError)
|
||||
|
@ -79,14 +99,14 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
|||
if (!silent)
|
||||
showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||
|
||||
return result;
|
||||
return QByteArray();
|
||||
}
|
||||
else if (dns.error() != QDnsLookup::NoError)
|
||||
{
|
||||
if (!silent)
|
||||
showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||
|
||||
return result;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
const QList<QDnsTextRecord> textRecords = dns.textRecords();
|
||||
|
@ -95,7 +115,7 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
|||
if (!silent)
|
||||
showWarning(tr("No text record found", "Error with the DNS"));
|
||||
|
||||
return result;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
const QList<QByteArray> textRecordValues = textRecords.last().values();
|
||||
|
@ -104,13 +124,20 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
|||
if (!silent)
|
||||
showWarning(tr("Unexpected number of values in text record", "Error with the DNS"));
|
||||
|
||||
return result;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
result = textRecordValues.first();
|
||||
return result;
|
||||
return textRecordValues.first();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Send query to DNS to find Tox Id.
|
||||
@note Will *NOT* fallback on queryTox1 anymore.
|
||||
@param server Server to sending query.
|
||||
@param record Should look like user@domain.tld.
|
||||
@param silent If true, there will be no output on error.
|
||||
@return Tox Id string.
|
||||
*/
|
||||
QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool silent)
|
||||
{
|
||||
QByteArray nameData = record.left(record.indexOf('@')).toUtf8(), id, realRecord;
|
||||
|
@ -190,44 +217,40 @@ fallbackOnTox1:
|
|||
return toxIdStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Tries to map a text string to a ToxId struct, will query Tox DNS records if necessary.
|
||||
@param address Adress to search for Tox ID.
|
||||
@param silent If true, there will be no output on error.
|
||||
@return Found Tox Id.
|
||||
*/
|
||||
ToxId ToxDNS::resolveToxAddress(const QString &address, bool silent)
|
||||
{
|
||||
ToxId toxId;
|
||||
|
||||
if (address.isEmpty())
|
||||
{
|
||||
return toxId;
|
||||
}
|
||||
else if (ToxId::isToxId(address))
|
||||
{
|
||||
toxId = ToxId(address);
|
||||
return toxId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're querying one of our pinned server, do a toxdns3 request directly
|
||||
QString servname = address.mid(address.indexOf('@')+1);
|
||||
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers)
|
||||
{
|
||||
if (servname == pin.name)
|
||||
{
|
||||
toxId = ToxId(queryTox3(pin, address, silent));
|
||||
return toxId;
|
||||
}
|
||||
}
|
||||
return ToxId();
|
||||
|
||||
// Otherwise try toxdns3 if we can get a pubkey or fallback to toxdns1
|
||||
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true);
|
||||
if (!pubkey.isEmpty())
|
||||
{
|
||||
pubkey = QByteArray::fromHex(pubkey);
|
||||
if (ToxId::isToxId(address))
|
||||
return ToxId(address);
|
||||
|
||||
QByteArray servnameData = servname.toUtf8();
|
||||
ToxDNS::tox3_server server;
|
||||
server.name = servnameData.data();
|
||||
server.pubkey = (uint8_t*)pubkey.data();
|
||||
toxId = ToxId(queryTox3(server, address, silent));
|
||||
}
|
||||
return toxId;
|
||||
// If we're querying one of our pinned servers, do a toxdns3 request directly
|
||||
QString servname = address.mid(address.indexOf('@')+1);
|
||||
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers)
|
||||
{
|
||||
if (servname == pin.name)
|
||||
return ToxId(queryTox3(pin, address, silent));
|
||||
}
|
||||
|
||||
// Otherwise try toxdns3 if we can get a pubkey or fallback to toxdns1
|
||||
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true);
|
||||
if (!pubkey.isEmpty())
|
||||
{
|
||||
pubkey = QByteArray::fromHex(pubkey);
|
||||
|
||||
QByteArray servnameData = servname.toUtf8();
|
||||
ToxDNS::tox3_server server;
|
||||
server.name = servnameData.data();
|
||||
server.pubkey = (uint8_t*)pubkey.data();
|
||||
return ToxId(queryTox3(server, address, silent));
|
||||
}
|
||||
|
||||
return ToxId();
|
||||
}
|
||||
|
|
|
@ -26,34 +26,29 @@
|
|||
#include <QDnsLookup>
|
||||
#include <QObject>
|
||||
|
||||
/// Handles tox1 and tox3 DNS queries
|
||||
class ToxDNS : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct tox3_server ///< Represents a tox3 server
|
||||
struct tox3_server
|
||||
{
|
||||
tox3_server()=default;
|
||||
tox3_server(const char* _name, uint8_t _pk[32]):name{_name},pubkey{_pk}{}
|
||||
|
||||
const char* name; ///< Hostname of the server, e.g. toxme.se
|
||||
uint8_t* pubkey; ///< Public key of the tox3 server, usually 256bit long
|
||||
const char* name;
|
||||
uint8_t* pubkey;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Tries to map a text string to a ToxId struct, will query Tox DNS records if necessary
|
||||
static ToxId resolveToxAddress(const QString& address, bool silent=true);
|
||||
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true); ///< Record should look like user@domain.tld, will *NOT* fallback on queryTox1 anymore
|
||||
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true);
|
||||
|
||||
protected:
|
||||
static void showWarning(const QString& message);
|
||||
ToxDNS()=default;
|
||||
|
||||
private:
|
||||
/// Try to fetch the first entry of the given TXT record
|
||||
/// Returns an empty object on failure. May block for up to ~3s
|
||||
/// May display message boxes on error if silent if false
|
||||
static QByteArray fetchLastTextRecord(const QString& record, bool silent=true);
|
||||
|
||||
public:
|
||||
|
|
|
@ -30,6 +30,14 @@
|
|||
#include <string>
|
||||
#include <ctime>
|
||||
|
||||
/**
|
||||
@class Toxme
|
||||
@brief This class implements a client for the toxme.se API
|
||||
|
||||
@note The class is thread safe
|
||||
@note May process events while waiting for blocking calls
|
||||
*/
|
||||
|
||||
QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError &error)
|
||||
{
|
||||
if (error)
|
||||
|
@ -136,6 +144,11 @@ QByteArray Toxme::prepareEncryptedJson(QString url, int action, QString payload)
|
|||
return json.toUtf8();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Converts a toxme address to a Tox ID.
|
||||
@param address Toxme address.
|
||||
@return Found ToxId (an empty ID on error).
|
||||
*/
|
||||
ToxId Toxme::lookup(QString address)
|
||||
{
|
||||
// JSON injection ?
|
||||
|
@ -204,6 +217,16 @@ Toxme::ExecCode Toxme::extractError(QString json)
|
|||
return ExecCode(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Creates a new toxme address associated with a Tox ID.
|
||||
@param[out] code Tox error code @see getErrorMessage.
|
||||
@param[in] server Create toxme account on this server.
|
||||
@param[in] id ToxId of current user.
|
||||
@param[in] address Create toxme account with this adress.
|
||||
@param[in] keepPrivate If true, the address will not be published on toxme site.
|
||||
@param[in] bio A short optional description of yourself if you want to publish your address.
|
||||
@return password on success, else sets code parameter and returns an empty QString.
|
||||
*/
|
||||
QString Toxme::createAddress(ExecCode &code, QString server, ToxId id, QString address,
|
||||
bool keepPrivate, QString bio)
|
||||
{
|
||||
|
@ -271,6 +294,12 @@ QString Toxme::getPass(QString json, ExecCode &code) {
|
|||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Deletes the address associated with your current Tox ID.
|
||||
@param server Server to delete the address from.
|
||||
@param id ToxId to delete.
|
||||
@return Status code returned from server.
|
||||
*/
|
||||
Toxme::ExecCode Toxme::deleteAddress(QString server, ToxId id)
|
||||
{
|
||||
const QString payload{"{\"public_key\":\""+id.toString().left(64)+"\","
|
||||
|
@ -289,10 +318,10 @@ Toxme::ExecCode Toxme::deleteAddress(QString server, ToxId id)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Return string of the corresponding error code
|
||||
* @param errorCode Code to get error message
|
||||
* @return Source error message
|
||||
*/
|
||||
@brief Return string of the corresponding error code
|
||||
@param errorCode Code to get error message
|
||||
@return Source error message
|
||||
*/
|
||||
QString Toxme::getErrorMessage(int errorCode)
|
||||
{
|
||||
switch (errorCode) {
|
||||
|
@ -336,10 +365,10 @@ QString Toxme::getErrorMessage(int errorCode)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Return translated error message
|
||||
* @param errorCode Code to translate
|
||||
* @return Translated Toxme error message
|
||||
*/
|
||||
@brief Return translated error message
|
||||
@param errorCode Code to translate
|
||||
@return Translated Toxme error message
|
||||
*/
|
||||
QString Toxme::translateErrorMessage(int errorCode)
|
||||
{
|
||||
switch (errorCode) {
|
||||
|
|
|
@ -30,9 +30,6 @@
|
|||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
/// This class implements a client for the toxme.se API
|
||||
/// The class is thread safe
|
||||
/// May process events while waiting for blocking calls
|
||||
class Toxme
|
||||
{
|
||||
public:
|
||||
|
@ -45,22 +42,15 @@ public:
|
|||
NoPassword = 4
|
||||
};
|
||||
|
||||
/// Converts a toxme.se address to a Tox ID, returns an empty ID on error
|
||||
static ToxId lookup(QString address);
|
||||
/// Creates a new toxme.se address associated with a Tox ID.
|
||||
/// If keepPrivate, the address will not be published on toxme.se
|
||||
/// The bio is a short optional description of yourself if you want to publish your address.
|
||||
/// If it passed without error, return password, else return errorCode in QString
|
||||
static QString createAddress(ExecCode &code, QString server, ToxId id, QString address,
|
||||
bool keepPrivate=true, QString bio=QString());
|
||||
/// Deletes the address associated with your current Tox ID
|
||||
static ExecCode deleteAddress(QString server, ToxId id);
|
||||
/// Return string of the corresponding error code
|
||||
static QString getErrorMessage(int errorCode);
|
||||
static QString translateErrorMessage(int errorCode);
|
||||
|
||||
private:
|
||||
Toxme()=delete;
|
||||
Toxme() = delete;
|
||||
static QByteArray makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError &error);
|
||||
static QByteArray prepareEncryptedJson(QString url, int action, QString payload);
|
||||
static QByteArray getServerPubkey(QString url, QNetworkReply::NetworkError &error);
|
||||
|
|
|
@ -44,6 +44,12 @@ bool toxURIEventHandler(const QByteArray& eventData)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Shows a dialog asking whether or not to add this tox address as a friend.
|
||||
@note Will wait until the core is ready first.
|
||||
@param toxURI Tox URI to try to add.
|
||||
@return True, if tox URI is correct, false otherwise.
|
||||
*/
|
||||
bool handleToxURI(const QString &toxURI)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
|
@ -75,6 +81,7 @@ bool handleToxURI(const QString &toxURI)
|
|||
.arg(Nexus::getCore()->getUsername()));
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
Core::getInstance()->requestFriendship(toxId, dialog.getRequestMessage());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
|
||||
#include <QDialog>
|
||||
|
||||
/// Shows a dialog asking whether or not to add this tox address as a friend
|
||||
/// Will wait until the core is ready first
|
||||
bool handleToxURI(const QString& toxURI);
|
||||
|
||||
// Internals
|
||||
|
|
|
@ -43,6 +43,14 @@
|
|||
#include <QSignalMapper>
|
||||
#endif
|
||||
|
||||
/**
|
||||
@class Nexus
|
||||
|
||||
This class is in charge of connecting various systems together
|
||||
and forwarding signals appropriately to the right objects,
|
||||
it is in charge of starting the GUI and the Core.
|
||||
*/
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(ToxAV*)
|
||||
|
||||
static Nexus* nexus{nullptr};
|
||||
|
@ -66,6 +74,11 @@ Nexus::~Nexus()
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
Sets up invariants and calls showLogin
|
||||
Hides the login screen and shows the GUI for the given profile.
|
||||
Will delete the current GUI, if it exists.
|
||||
*/
|
||||
void Nexus::start()
|
||||
{
|
||||
qDebug() << "Starting up";
|
||||
|
@ -131,6 +144,9 @@ void Nexus::start()
|
|||
showLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Hides the main GUI, delete the profile, and shows the login screen
|
||||
*/
|
||||
void Nexus::showLogin()
|
||||
{
|
||||
delete widget;
|
||||
|
@ -201,6 +217,9 @@ void Nexus::showMainGUI()
|
|||
profile->startCore();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
Nexus& Nexus::getInstance()
|
||||
{
|
||||
if (!nexus)
|
||||
|
@ -215,6 +234,10 @@ void Nexus::destroyInstance()
|
|||
nexus = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get core instance.
|
||||
@return nullptr if not started, core instance otherwise.
|
||||
*/
|
||||
Core* Nexus::getCore()
|
||||
{
|
||||
Nexus& nexus = getInstance();
|
||||
|
@ -224,11 +247,19 @@ Core* Nexus::getCore()
|
|||
return nexus.profile->getCore();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get current user profile.
|
||||
@return nullptr if not started, profile otherwise.
|
||||
*/
|
||||
Profile* Nexus::getProfile()
|
||||
{
|
||||
return getInstance().profile;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Unload the current profile, if any, and replaces it.
|
||||
@param profile Profile to set.
|
||||
*/
|
||||
void Nexus::setProfile(Profile* profile)
|
||||
{
|
||||
getInstance().profile = profile;
|
||||
|
@ -236,6 +267,10 @@ void Nexus::setProfile(Profile* profile)
|
|||
Settings::getInstance().loadPersonal(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get desktop GUI widget.
|
||||
@return nullptr if not started, desktop widget otherwise.
|
||||
*/
|
||||
Widget* Nexus::getDesktopGUI()
|
||||
{
|
||||
return getInstance().widget;
|
||||
|
@ -250,6 +285,11 @@ QString Nexus::getSupportedImageFilter()
|
|||
return tr("Images (%1)", "filetype filter").arg(res.left(res.size()-1));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Dangerous way to find out if a path is writable.
|
||||
@param filepath Path to file which should be deleted.
|
||||
@return True, if file writeable, false otherwise.
|
||||
*/
|
||||
bool Nexus::tryRemoveFile(const QString& filepath)
|
||||
{
|
||||
QFile tmp(filepath);
|
||||
|
@ -258,6 +298,9 @@ bool Nexus::tryRemoveFile(const QString& filepath)
|
|||
return writable;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Calls showLogin asynchronously, so we can safely logout from within the main GUI
|
||||
*/
|
||||
void Nexus::showLoginLater()
|
||||
{
|
||||
GUI::setEnabled(false);
|
||||
|
|
21
src/nexus.h
21
src/nexus.h
|
@ -37,30 +37,25 @@ class QActionGroup;
|
|||
class QSignalMapper;
|
||||
#endif
|
||||
|
||||
/// This class is in charge of connecting various systems together
|
||||
/// and forwarding signals appropriately to the right objects
|
||||
/// It is in charge of starting the GUI and the Core
|
||||
class Nexus : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void start(); ///< Sets up invariants and calls showLogin
|
||||
/// Hides the login screen and shows the GUI for the given profile.
|
||||
/// Will delete the current GUI, if it exists.
|
||||
void start();
|
||||
void showMainGUI();
|
||||
|
||||
static Nexus& getInstance();
|
||||
static void destroyInstance();
|
||||
static Core* getCore(); ///< Will return 0 if not started
|
||||
static Profile* getProfile(); ///< Will return 0 if not started
|
||||
static void setProfile(Profile* profile); ///< Delete the current profile, if any, and replaces it
|
||||
static Widget* getDesktopGUI(); ///< Will return 0 if not started
|
||||
static Core* getCore();
|
||||
static Profile* getProfile();
|
||||
static void setProfile(Profile* profile);
|
||||
static Widget* getDesktopGUI();
|
||||
static QString getSupportedImageFilter();
|
||||
static bool tryRemoveFile(const QString& filepath); ///< Dangerous way to find out if a path is writable
|
||||
static bool tryRemoveFile(const QString& filepath);
|
||||
|
||||
public slots:
|
||||
void showLogin(); ///< Hides the man GUI, delete the profile, and shows the login screen
|
||||
void showLoginLater(); ///< Calls showLogin asynchronously, so we can safely logout from within the main GUI
|
||||
void showLogin();
|
||||
void showLoginLater();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
public:
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
#include <QDebug>
|
||||
#include <QSqlError>
|
||||
|
||||
/**
|
||||
@var static TOX_PASS_KEY EncryptedDb::decryptionKey
|
||||
@note When importing, the decryption key may not be the same as the profile key
|
||||
*/
|
||||
|
||||
qint64 EncryptedDb::encryptedChunkSize = 4096;
|
||||
qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ private:
|
|||
static qint64 plainChunkSize;
|
||||
static qint64 encryptedChunkSize;
|
||||
|
||||
static TOX_PASS_KEY decryptionKey; ///< When importing, the decryption key may not be the same as the profile key
|
||||
static TOX_PASS_KEY decryptionKey;
|
||||
|
||||
qint64 chunkPosition;
|
||||
QByteArray buffer;
|
||||
|
|
|
@ -14,6 +14,60 @@
|
|||
|
||||
#include <sqlcipher/sqlite3.h>
|
||||
|
||||
/**
|
||||
@class RawDatabase
|
||||
@brief Implements a low level RAII interface to a SQLCipher (SQlite3) database.
|
||||
|
||||
Thread-safe, does all database operations on a worker thread.
|
||||
The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is undefined.
|
||||
|
||||
@var QMutex RawDatabase::transactionsMutex;
|
||||
@brief Protects pendingTransactions
|
||||
*/
|
||||
|
||||
/**
|
||||
@class Query
|
||||
@brief A query to be executed by the database.
|
||||
|
||||
Can be composed of one or more SQL statements in the query,
|
||||
optional BLOB parameters to be bound, and callbacks fired when the query is executed
|
||||
Calling any database method from a query callback is undefined behavior.
|
||||
|
||||
@var QByteArray RawDatabase::Query::query
|
||||
@brief UTF-8 query string
|
||||
|
||||
@var QVector<QByteArray> RawDatabase::Query::blobs
|
||||
@brief Bound data blobs
|
||||
|
||||
@var std::function<void(int64_t)> RawDatabase::Query::insertCallback
|
||||
@brief Called after execution with the last insert rowid
|
||||
|
||||
@var std::function<void(const QVector<QVariant>&)> RawDatabase::Query::rowCallback
|
||||
@brief Called during execution for each row
|
||||
|
||||
@var QVector<sqlite3_stmt*> RawDatabase::Query::statements
|
||||
@brief Statements to be compiled from the query
|
||||
*/
|
||||
|
||||
/**
|
||||
@struct Transaction
|
||||
@brief SQL transactions to be processed.
|
||||
|
||||
A transaction is made of queries, which can have bound BLOBs.
|
||||
|
||||
@var std::atomic_bool* RawDatabase::Transaction::success = nullptr;
|
||||
@brief If not a nullptr, the result of the transaction will be set
|
||||
|
||||
@var std::atomic_bool* RawDatabase::Transaction::done = nullptr;
|
||||
@brief If not a nullptr, will be set to true when the transaction has been executed
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Tries to open a database.
|
||||
@param path Path to database.
|
||||
@param password If empty, the database will be opened unencrypted.
|
||||
Otherwise we will use toxencryptsave to derive a key and encrypt the database.
|
||||
*/
|
||||
RawDatabase::RawDatabase(const QString &path, const QString& password)
|
||||
: workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)}
|
||||
{
|
||||
|
@ -33,6 +87,12 @@ RawDatabase::~RawDatabase()
|
|||
workerThread->wait(50);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Tries to open the database with the given (possibly empty) key.
|
||||
@param path Path to database.
|
||||
@param hexKey Hex representation of the key in string.
|
||||
@return True if success, false otherwise.
|
||||
*/
|
||||
bool RawDatabase::open(const QString& path, const QString &hexKey)
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
|
@ -75,6 +135,9 @@ bool RawDatabase::open(const QString& path, const QString &hexKey)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Close the database and free its associated resources.
|
||||
*/
|
||||
void RawDatabase::close()
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
|
@ -89,22 +152,41 @@ void RawDatabase::close()
|
|||
qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks, that the database is open.
|
||||
@return True if the database was opened successfully.
|
||||
*/
|
||||
bool RawDatabase::isOpen()
|
||||
{
|
||||
// We don't need thread safety since only the ctor/dtor can write this pointer
|
||||
return sqlite != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Executes a SQL transaction synchronously.
|
||||
@param statement Statement to execute.
|
||||
@return Whether the transaction was successful.
|
||||
*/
|
||||
bool RawDatabase::execNow(const QString& statement)
|
||||
{
|
||||
return execNow(Query{statement});
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Executes a SQL transaction synchronously.
|
||||
@param statement Statement to execute.
|
||||
@return Whether the transaction was successful.
|
||||
*/
|
||||
bool RawDatabase::execNow(const RawDatabase::Query &statement)
|
||||
{
|
||||
return execNow(QVector<Query>{statement});
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Executes a SQL transaction synchronously.
|
||||
@param statements List of statements to execute.
|
||||
@return Whether the transaction was successful.
|
||||
*/
|
||||
bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
|
||||
{
|
||||
if (!sqlite)
|
||||
|
@ -134,6 +216,10 @@ bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
|
|||
return success.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Executes a SQL transaction asynchronously.
|
||||
@param statement Statement to execute.
|
||||
*/
|
||||
void RawDatabase::execLater(const QString &statement)
|
||||
{
|
||||
execLater(Query{statement});
|
||||
|
@ -162,11 +248,20 @@ void RawDatabase::execLater(const QVector<RawDatabase::Query> &statements)
|
|||
QMetaObject::invokeMethod(this, "process");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Waits until all the pending transactions are executed.
|
||||
*/
|
||||
void RawDatabase::sync()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Changes the database password, encrypting or decrypting if necessary.
|
||||
@param password If password is empty, the database will be decrypted.
|
||||
@return True if success, false otherwise.
|
||||
@note Will process all transactions before changing the password.
|
||||
*/
|
||||
bool RawDatabase::setPassword(const QString& password)
|
||||
{
|
||||
if (!sqlite)
|
||||
|
@ -260,6 +355,13 @@ bool RawDatabase::setPassword(const QString& password)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Moves the database file on disk to match the new path.
|
||||
@param newPath Path to move database file.
|
||||
@return True if success, false otherwise.
|
||||
|
||||
@note Will process all transactions before renaming
|
||||
*/
|
||||
bool RawDatabase::rename(const QString &newPath)
|
||||
{
|
||||
if (!sqlite)
|
||||
|
@ -291,6 +393,11 @@ bool RawDatabase::rename(const QString &newPath)
|
|||
return open(path, currentHexKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Deletes the on disk database file after closing it.
|
||||
@note Will process all transactions before deletings.
|
||||
@return True if success, false otherwise.
|
||||
*/
|
||||
bool RawDatabase::remove()
|
||||
{
|
||||
if (!sqlite)
|
||||
|
@ -311,7 +418,11 @@ bool RawDatabase::remove()
|
|||
return QFile::remove(path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief Derives a 256bit key from the password and returns it hex-encoded
|
||||
@param password Password to decrypt database
|
||||
@return String representation of key
|
||||
*/
|
||||
QString RawDatabase::deriveKey(const QString &password)
|
||||
{
|
||||
if (password.isEmpty())
|
||||
|
@ -327,6 +438,12 @@ QString RawDatabase::deriveKey(const QString &password)
|
|||
return QByteArray((char*)key.key, 32).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Implements the actual processing of pending transactions.
|
||||
Unqueues, compiles, binds and executes queries, then notifies of results
|
||||
|
||||
@warning MUST only be called from the worker thread
|
||||
*/
|
||||
void RawDatabase::process()
|
||||
{
|
||||
assert(QThread::currentThread() == workerThread.get());
|
||||
|
@ -460,6 +577,12 @@ void RawDatabase::process()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Extracts a variant from one column of a result row depending on the column type.
|
||||
@param stmt Statement to execute.
|
||||
@param col Number of column to extract.
|
||||
@return Extracted data.
|
||||
*/
|
||||
QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col)
|
||||
{
|
||||
int type = sqlite3_column_type(stmt, col);
|
||||
|
|
|
@ -15,17 +15,11 @@
|
|||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
/// Implements a low level RAII interface to a SQLCipher (SQlite3) database
|
||||
/// Thread-safe, does all database operations on a worker thread
|
||||
/// The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is undefined
|
||||
class RawDatabase : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// A query to be executed by the database. Can be composed of one or more SQL statements in the query,
|
||||
/// optional BLOB parameters to be bound, and callbacks fired when the query is executed
|
||||
/// Calling any database method from a query callback is undefined behavior
|
||||
class Query
|
||||
{
|
||||
public:
|
||||
|
@ -37,71 +31,49 @@ public:
|
|||
: query{query.toUtf8()}, rowCallback{rowCallback} {}
|
||||
Query() = default;
|
||||
private:
|
||||
QByteArray query; ///< UTF-8 query string
|
||||
QVector<QByteArray> blobs; ///< Bound data blobs
|
||||
std::function<void(int64_t)> insertCallback; ///< Called after execution with the last insert rowid
|
||||
std::function<void(const QVector<QVariant>&)> rowCallback; ///< Called during execution for each row
|
||||
QVector<sqlite3_stmt*> statements; ///< Statements to be compiled from the query
|
||||
QByteArray query;
|
||||
QVector<QByteArray> blobs;
|
||||
std::function<void(int64_t)> insertCallback;
|
||||
std::function<void(const QVector<QVariant>&)> rowCallback;
|
||||
QVector<sqlite3_stmt*> statements;
|
||||
|
||||
friend class RawDatabase;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Tries to open a database
|
||||
/// If password is empty, the database will be opened unencrypted
|
||||
/// Otherwise we will use toxencryptsave to derive a key and encrypt the database
|
||||
RawDatabase(const QString& path, const QString& password);
|
||||
~RawDatabase();
|
||||
bool isOpen(); ///< Returns true if the database was opened successfully
|
||||
/// Executes a SQL transaction synchronously.
|
||||
/// Returns whether the transaction was successful.
|
||||
bool isOpen();
|
||||
|
||||
bool execNow(const QString& statement);
|
||||
bool execNow(const Query& statement);
|
||||
bool execNow(const QVector<Query>& statements);
|
||||
/// Executes a SQL transaction asynchronously.
|
||||
|
||||
void execLater(const QString& statement);
|
||||
void execLater(const Query& statement);
|
||||
void execLater(const QVector<Query>& statements);
|
||||
/// Waits until all the pending transactions are executed
|
||||
|
||||
void sync();
|
||||
|
||||
public slots:
|
||||
/// Changes the database password, encrypting or decrypting if necessary
|
||||
/// If password is empty, the database will be decrypted
|
||||
/// Will process all transactions before changing the password
|
||||
bool setPassword(const QString& password);
|
||||
/// Moves the database file on disk to match the new path
|
||||
/// Will process all transactions before renaming
|
||||
bool rename(const QString& newPath);
|
||||
/// Deletes the on disk database file after closing it
|
||||
/// Will process all transactions before deletings
|
||||
bool remove();
|
||||
|
||||
protected slots:
|
||||
/// Tries to open the database with the given (possibly empty) key
|
||||
bool open(const QString& path, const QString& hexKey = {});
|
||||
/// Closes the database and free its associated resources
|
||||
void close();
|
||||
/// Implements the actual processing of pending transactions
|
||||
/// Unqueues, compiles, binds and executes queries, then notifies of results
|
||||
/// MUST only be called from the worker thread
|
||||
void process();
|
||||
|
||||
protected:
|
||||
/// Derives a 256bit key from the password and returns it hex-encoded
|
||||
static QString deriveKey(const QString &password);
|
||||
/// Extracts a variant from one column of a result row depending on the column type
|
||||
static QVariant extractData(sqlite3_stmt* stmt, int col);
|
||||
|
||||
private:
|
||||
/// SQL transactions to be processed
|
||||
/// A transaction is made of queries, which can have bound BLOBs
|
||||
struct Transaction
|
||||
{
|
||||
QVector<Query> queries;
|
||||
/// If not a nullptr, the result of the transaction will be set
|
||||
std::atomic_bool* success = nullptr;
|
||||
/// If not a nullptr, will be set to true when the transaction has been executed
|
||||
std::atomic_bool* done = nullptr;
|
||||
};
|
||||
|
||||
|
@ -109,7 +81,6 @@ private:
|
|||
sqlite3* sqlite;
|
||||
std::unique_ptr<QThread> workerThread;
|
||||
QQueue<Transaction> pendingTransactions;
|
||||
/// Protects pendingTransactions
|
||||
QMutex transactionsMutex;
|
||||
QString path;
|
||||
QString currentHexKey;
|
||||
|
|
|
@ -8,12 +8,32 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
@class History
|
||||
@brief Interacts with the profile database to save the chat history.
|
||||
|
||||
@var QHash<QString, int64_t> History::peers
|
||||
@brief Maps friend public keys to unique IDs by index.
|
||||
Caches mappings to speed up message saving.
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Opens the profile database and prepares to work with the history.
|
||||
@param profileName Profile name to load.
|
||||
@param password If empty, the database will be opened unencrypted.
|
||||
*/
|
||||
History::History(const QString &profileName, const QString &password)
|
||||
: db{getDbPath(profileName), password}
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Opens the profile database, and import from the old database.
|
||||
@param profileName Profile name to load.
|
||||
@param password If empty, the database will be opened unencrypted.
|
||||
@param oldHistory Old history to import.
|
||||
*/
|
||||
History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory)
|
||||
: History{profileName, password}
|
||||
{
|
||||
|
@ -27,26 +47,45 @@ History::~History()
|
|||
db.sync();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if the database was opened successfully
|
||||
@return True if database if opened, false otherwise.
|
||||
*/
|
||||
bool History::isValid()
|
||||
{
|
||||
return db.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Changes the database password, will encrypt or decrypt if necessary.
|
||||
@param password Password to set.
|
||||
*/
|
||||
void History::setPassword(const QString& password)
|
||||
{
|
||||
db.setPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Moves the database file on disk to match the new name.
|
||||
@param newName New name.
|
||||
*/
|
||||
void History::rename(const QString &newName)
|
||||
{
|
||||
db.rename(getDbPath(newName));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Deletes the on-disk database file.
|
||||
@return True if success, false otherwise.
|
||||
*/
|
||||
bool History::remove()
|
||||
{
|
||||
return db.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Erases all the chat history from the database.
|
||||
*/
|
||||
void History::eraseHistory()
|
||||
{
|
||||
db.execNow("DELETE FROM faux_offline_pending;"
|
||||
|
@ -56,6 +95,10 @@ void History::eraseHistory()
|
|||
"VACUUM;");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Erases the chat history with one friend.
|
||||
@param friendPk Friend public key to erase.
|
||||
*/
|
||||
void History::removeFriendHistory(const QString &friendPk)
|
||||
{
|
||||
if (!peers.contains(friendPk))
|
||||
|
@ -81,6 +124,16 @@ void History::removeFriendHistory(const QString &friendPk)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Generate query to insert new message in database
|
||||
@param friendPk Friend publick key to save.
|
||||
@param message Message to save.
|
||||
@param sender Sender to save.
|
||||
@param time Time of message sending.
|
||||
@param isSent True if message was already sent.
|
||||
@param dispName Name, which should be displayed.
|
||||
@param insertIdCallback Function, called after query execution.
|
||||
*/
|
||||
QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &friendPk, const QString &message,
|
||||
const QString &sender, const QDateTime &time, bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback)
|
||||
|
@ -139,12 +192,29 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
|
|||
return queries;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Saves a chat message in the database.
|
||||
@param friendPk Friend publick key to save.
|
||||
@param message Message to save.
|
||||
@param sender Sender to save.
|
||||
@param time Time of message sending.
|
||||
@param isSent True if message was already sent.
|
||||
@param dispName Name, which should be displayed.
|
||||
@param insertIdCallback Function, called after query execution.
|
||||
*/
|
||||
void History::addNewMessage(const QString &friendPk, const QString &message, const QString &sender,
|
||||
const QDateTime &time, bool isSent, QString dispName, std::function<void(int64_t)> insertIdCallback)
|
||||
{
|
||||
db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Fetches chat messages from the database.
|
||||
@param friendPk Friend publick key to fetch.
|
||||
@param from Start of period to fetch.
|
||||
@param to End of period to fetch.
|
||||
@return List of messages.
|
||||
*/
|
||||
QList<History::HistMessage> History::getChatHistory(const QString &friendPk, const QDateTime &from, const QDateTime &to)
|
||||
{
|
||||
QList<HistMessage> messages;
|
||||
|
@ -174,16 +244,30 @@ QList<History::HistMessage> History::getChatHistory(const QString &friendPk, con
|
|||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Marks a message as sent.
|
||||
Removing message from the faux-offline pending messages list.
|
||||
|
||||
@param id Message ID.
|
||||
*/
|
||||
void History::markAsSent(qint64 id)
|
||||
{
|
||||
db.execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(id));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Retrieves the path to the database file for a given profile.
|
||||
@param profileName Profile name.
|
||||
@return Path to database.
|
||||
*/
|
||||
QString History::getDbPath(const QString &profileName)
|
||||
{
|
||||
return Settings::getInstance().getSettingsDirPath() + profileName + ".db";
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Makes sure the history tables are created
|
||||
*/
|
||||
void History::init()
|
||||
{
|
||||
if (!isValid())
|
||||
|
@ -207,6 +291,10 @@ void History::init()
|
|||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Imports messages from the old history file.
|
||||
@param oldHistory Old history to import.
|
||||
*/
|
||||
void History::import(const HistoryKeeper &oldHistory)
|
||||
{
|
||||
if (!isValid())
|
||||
|
|
|
@ -12,7 +12,6 @@ class Profile;
|
|||
class HistoryKeeper;
|
||||
class RawDatabase;
|
||||
|
||||
/// Interacts with the profile database to save the chat history
|
||||
class History
|
||||
{
|
||||
public:
|
||||
|
@ -31,40 +30,26 @@ public:
|
|||
};
|
||||
|
||||
public:
|
||||
/// Opens the profile database and prepares to work with the history
|
||||
/// If password is empty, the database will be opened unencrypted
|
||||
History(const QString& profileName, const QString& password);
|
||||
/// Opens the profile database, and import from the old database
|
||||
/// If password is empty, the database will be opened unencrypted
|
||||
History(const QString& profileName, const QString& password, const HistoryKeeper& oldHistory);
|
||||
~History();
|
||||
/// Checks if the database was opened successfully
|
||||
|
||||
bool isValid();
|
||||
/// Imports messages from the old history file
|
||||
void import(const HistoryKeeper& oldHistory);
|
||||
/// Changes the database password, will encrypt or decrypt if necessary
|
||||
void setPassword(const QString& password);
|
||||
/// Moves the database file on disk to match the new name
|
||||
void rename(const QString& newName);
|
||||
/// Deletes the on-disk database file
|
||||
bool remove();
|
||||
|
||||
/// Erases all the chat history from the database
|
||||
void eraseHistory();
|
||||
/// Erases the chat history with one friend
|
||||
void removeFriendHistory(const QString& friendPk);
|
||||
/// Saves a chat message in the database
|
||||
void addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
|
||||
const QDateTime &time, bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback={});
|
||||
/// Fetches chat messages from the database
|
||||
|
||||
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to);
|
||||
/// Marks a message as sent, removing it from the faux-offline pending messages list
|
||||
void markAsSent(qint64 id);
|
||||
/// Retrieves the path to the database file for a given profile.
|
||||
static QString getDbPath(const QString& profileName);
|
||||
protected:
|
||||
/// Makes sure the history tables are created
|
||||
void init();
|
||||
QVector<RawDatabase::Query> generateNewMessageQueries(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime &time, bool isSent, QString dispName,
|
||||
|
@ -72,8 +57,7 @@ protected:
|
|||
|
||||
private:
|
||||
RawDatabase db;
|
||||
// Cached mappings to speed up message saving
|
||||
QHash<QString, int64_t> peers; ///< Maps friend public keys to unique IDs by index
|
||||
QHash<QString, int64_t> peers;
|
||||
};
|
||||
|
||||
#endif // HISTORY_H
|
||||
|
|
|
@ -35,9 +35,19 @@
|
|||
#include "src/persistence/db/plaindb.h"
|
||||
#include "src/persistence/db/encrypteddb.h"
|
||||
|
||||
/**
|
||||
@class HistoryKeeper
|
||||
@brief THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
|
||||
@deprecated See the History class instead
|
||||
@warning DO NOT USE!
|
||||
*/
|
||||
|
||||
static HistoryKeeper *historyInstance = nullptr;
|
||||
QMutex HistoryKeeper::historyMutex;
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
HistoryKeeper *HistoryKeeper::getInstance(const Profile& profile)
|
||||
{
|
||||
historyMutex.lock();
|
||||
|
|
|
@ -27,12 +27,6 @@
|
|||
#include <QMutex>
|
||||
#include <tox/toxencryptsave.h>
|
||||
|
||||
/**
|
||||
* THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
|
||||
* DO NOT USE!
|
||||
* See the History class instead
|
||||
*/
|
||||
|
||||
class Profile;
|
||||
class GenericDdInterface;
|
||||
namespace Db { enum class syncType; }
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
#include <QMutexLocker>
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
@var static const int OfflineMsgEngine::offlineTimeout
|
||||
@brief timeout after which faux offline messages get to be re-sent.
|
||||
Originally was 2s, but since that was causing lots of duplicated
|
||||
messages on receiving end, make qTox be more lazy about re-sending
|
||||
should be 20s.
|
||||
*/
|
||||
|
||||
|
||||
const int OfflineMsgEngine::offlineTimeout = 20000;
|
||||
QMutex OfflineMsgEngine::globalMutex;
|
||||
|
||||
|
|
|
@ -57,10 +57,6 @@ private:
|
|||
QHash<int, int64_t> receipts;
|
||||
QMap<int64_t, MsgPtr> undeliveredMsgs;
|
||||
|
||||
// timeout after which faux offline messages get to be re-sent
|
||||
// originally was 2s, but since that was causing lots of duplicated
|
||||
// messages on receiving end, make qTox be more lazy about re-sending
|
||||
// should be 20s
|
||||
static const int offlineTimeout;
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,21 @@
|
|||
#include <QDebug>
|
||||
#include <sodium.h>
|
||||
|
||||
/**
|
||||
@class Profile
|
||||
@brief Manages user profiles.
|
||||
|
||||
@var bool Profile::newProfile
|
||||
@brief True if this is a newly created profile, with no .tox save file yet.
|
||||
|
||||
@var bool Profile::isRemoved
|
||||
@brief True if the profile has been removed by remove().
|
||||
|
||||
@var static constexpr int Profile::encryptHeaderSize = 8
|
||||
@brief How much data we need to read to check if the file is encrypted.
|
||||
@note Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined.
|
||||
*/
|
||||
|
||||
QVector<QString> Profile::profiles;
|
||||
|
||||
Profile::Profile(QString name, const QString &password, bool isNewProfile)
|
||||
|
@ -65,6 +80,14 @@ Profile::Profile(QString name, const QString &password, bool isNewProfile)
|
|||
QObject::connect(coreThread, &QThread::started, core, &Core::start);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Locks and loads an existing profile and creates the associate Core* instance.
|
||||
@param name Profile name.
|
||||
@param password Profile password.
|
||||
@return Returns a nullptr on error. Profile pointer otherwise.
|
||||
|
||||
@example If the profile is already in use return nullptr.
|
||||
*/
|
||||
Profile* Profile::loadProfile(QString name, const QString &password)
|
||||
{
|
||||
if (ProfileLocker::hasLock())
|
||||
|
@ -142,6 +165,14 @@ Profile* Profile::loadProfile(QString name, const QString &password)
|
|||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Creates a new profile and the associated Core* instance.
|
||||
@param name Username.
|
||||
@param password If password is not empty, the profile will be encrypted.
|
||||
@return Returns a nullptr on error. Profile pointer otherwise.
|
||||
|
||||
@example If the profile is already in use return nullptr.
|
||||
*/
|
||||
Profile* Profile::createProfile(QString name, QString password)
|
||||
{
|
||||
if (ProfileLocker::hasLock())
|
||||
|
@ -182,6 +213,11 @@ Profile::~Profile()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Lists all the files in the config dir with a given extension
|
||||
@param extension Raw extension, e.g. "jpeg" not ".jpeg".
|
||||
@return Vector of filenames.
|
||||
*/
|
||||
QVector<QString> Profile::getFilesByExt(QString extension)
|
||||
{
|
||||
QDir dir(Settings::getInstance().getSettingsDirPath());
|
||||
|
@ -195,6 +231,10 @@ QVector<QString> Profile::getFilesByExt(QString extension)
|
|||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Scan for profile, automatically importing them if needed.
|
||||
@warning NOT thread-safe.
|
||||
*/
|
||||
void Profile::scanProfiles()
|
||||
{
|
||||
profiles.clear();
|
||||
|
@ -222,6 +262,9 @@ QString Profile::getName() const
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Starts the Core thread
|
||||
*/
|
||||
void Profile::startCore()
|
||||
{
|
||||
coreThread->start();
|
||||
|
@ -232,6 +275,10 @@ bool Profile::isNewProfile()
|
|||
return newProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Loads the profile's .tox save from file, unencrypted
|
||||
@return Byte array of loaded profile save.
|
||||
*/
|
||||
QByteArray Profile::loadToxSave()
|
||||
{
|
||||
assert(!isRemoved);
|
||||
|
@ -290,6 +337,10 @@ fail:
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Saves the profile's .tox save, encrypted if needed.
|
||||
@warning Invalid on deleted profiles.
|
||||
*/
|
||||
void Profile::saveToxSave()
|
||||
{
|
||||
assert(core->isReady());
|
||||
|
@ -298,6 +349,11 @@ void Profile::saveToxSave()
|
|||
saveToxSave(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Write the .tox save, encrypted if needed.
|
||||
@param data Byte array of profile save.
|
||||
@warning Invalid on deleted profiles.
|
||||
*/
|
||||
void Profile::saveToxSave(QByteArray data)
|
||||
{
|
||||
assert(!isRemoved);
|
||||
|
@ -340,6 +396,12 @@ void Profile::saveToxSave(QByteArray data)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Gets the path of the avatar file cached by this profile and corresponding to this owner ID.
|
||||
@param ownerId Path to avatar of friend with this ID will returned.
|
||||
@param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile.
|
||||
@return Path to the avatar.
|
||||
*/
|
||||
QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
|
||||
{
|
||||
if (password.isEmpty() || forceUnencrypted)
|
||||
|
@ -357,11 +419,20 @@ QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
|
|||
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png";
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get our avatar from cache.
|
||||
@return Avatar as QPixmap.
|
||||
*/
|
||||
QPixmap Profile::loadAvatar()
|
||||
{
|
||||
return loadAvatar(core->getSelfId().publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get a contact's avatar from cache.
|
||||
@param ownerId Friend ID to load avatar.
|
||||
@return Avatar as QPixmap.
|
||||
*/
|
||||
QPixmap Profile::loadAvatar(const QString &ownerId)
|
||||
{
|
||||
QPixmap pic;
|
||||
|
@ -369,11 +440,22 @@ QPixmap Profile::loadAvatar(const QString &ownerId)
|
|||
return pic;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get a contact's avatar from cache
|
||||
@param ownerId Friend ID to load avatar.
|
||||
@return Avatar as QByteArray.
|
||||
*/
|
||||
QByteArray Profile::loadAvatarData(const QString &ownerId)
|
||||
{
|
||||
return loadAvatarData(ownerId, password);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get a contact's avatar from cache, with a specified profile password.
|
||||
@param ownerId Friend ID to load avatar.
|
||||
@param password Profile password to decrypt data.
|
||||
@return Avatar as QByteArray.
|
||||
*/
|
||||
QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &password)
|
||||
{
|
||||
QString path = avatarPath(ownerId);
|
||||
|
@ -401,6 +483,11 @@ QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &passwo
|
|||
return pic;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Save an avatar to cache.
|
||||
@param pic Picture to save.
|
||||
@param ownerId ID of avatar owner.
|
||||
*/
|
||||
void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
|
||||
{
|
||||
if (!password.isEmpty() && !pic.isEmpty())
|
||||
|
@ -425,6 +512,11 @@ void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the tox hash of a cached avatar.
|
||||
@param ownerId Friend ID to get hash.
|
||||
@return Avatar tox hash.
|
||||
*/
|
||||
QByteArray Profile::getAvatarHash(const QString &ownerId)
|
||||
{
|
||||
QByteArray pic = loadAvatarData(ownerId);
|
||||
|
@ -433,21 +525,36 @@ QByteArray Profile::getAvatarHash(const QString &ownerId)
|
|||
return avatarHash;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Removes our own avatar.
|
||||
*/
|
||||
void Profile::removeAvatar()
|
||||
{
|
||||
removeAvatar(core->getSelfId().publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks that the history is enabled in the settings, and loaded successfully for this profile.
|
||||
@return True if enabled, false otherwise.
|
||||
*/
|
||||
bool Profile::isHistoryEnabled()
|
||||
{
|
||||
return Settings::getInstance().getEnableLogging() && history;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get chat history.
|
||||
@return May return a nullptr if the history failed to load.
|
||||
*/
|
||||
History *Profile::getHistory()
|
||||
{
|
||||
return history.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Removes a cached avatar.
|
||||
@param ownerId Friend ID whose avater to delete.
|
||||
*/
|
||||
void Profile::removeAvatar(const QString &ownerId)
|
||||
{
|
||||
QFile::remove(avatarPath(ownerId));
|
||||
|
@ -461,11 +568,21 @@ bool Profile::exists(QString name)
|
|||
return QFile::exists(path+".tox");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks, if profile has a password.
|
||||
@return True if we have a password set (doesn't check the actual file on disk).
|
||||
*/
|
||||
bool Profile::isEncrypted() const
|
||||
{
|
||||
return !password.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if profile is encrypted.
|
||||
@note Checks the actual file on disk.
|
||||
@param name Profile name.
|
||||
@return True if profile is encrypted, false otherwise.
|
||||
*/
|
||||
bool Profile::isEncrypted(QString name)
|
||||
{
|
||||
uint8_t data[encryptHeaderSize] = {0};
|
||||
|
@ -483,6 +600,12 @@ bool Profile::isEncrypted(QString name)
|
|||
return tox_is_data_encrypted(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Removes the profile permanently.
|
||||
Updates the profiles vector.
|
||||
@return Vector of filenames that could not be removed.
|
||||
@warning It is invalid to call loadToxSave or saveToxSave on a deleted profile.
|
||||
*/
|
||||
QVector<QString> Profile::remove()
|
||||
{
|
||||
if (isRemoved)
|
||||
|
@ -546,6 +669,11 @@ QVector<QString> Profile::remove()
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Tries to rename the profile.
|
||||
@param newName New name for the profile.
|
||||
@return False on error, true otherwise.
|
||||
*/
|
||||
bool Profile::rename(QString newName)
|
||||
{
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name,
|
||||
|
@ -568,6 +696,10 @@ bool Profile::rename(QString newName)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks whether the password is valid.
|
||||
@return True, if password is valid, false otherwise.
|
||||
*/
|
||||
bool Profile::checkPassword()
|
||||
{
|
||||
if (isRemoved)
|
||||
|
@ -586,6 +718,9 @@ const TOX_PASS_KEY& Profile::getPasskey() const
|
|||
return passkey;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Delete core and restart a new one
|
||||
*/
|
||||
void Profile::restartCore()
|
||||
{
|
||||
GUI::setEnabled(false); // Core::reset re-enables it
|
||||
|
@ -594,6 +729,10 @@ void Profile::restartCore()
|
|||
QMetaObject::invokeMethod(core, "reset");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Changes the encryption password and re-saves everything with it
|
||||
@param newPassword Password for encryption.
|
||||
*/
|
||||
void Profile::setPassword(const QString &newPassword)
|
||||
{
|
||||
QByteArray avatar = loadAvatarData(core->getSelfId().publicKey);
|
||||
|
|
|
@ -32,76 +32,54 @@
|
|||
class Core;
|
||||
class QThread;
|
||||
|
||||
/// Manages user profiles
|
||||
class Profile
|
||||
{
|
||||
public:
|
||||
/// Locks and loads an existing profile and create the associate Core* instance
|
||||
/// Returns a nullptr on error, for example if the profile is already in use
|
||||
static Profile* loadProfile(QString name, const QString &password = QString());
|
||||
/// Creates a new profile and the associated Core* instance
|
||||
/// If password is not empty, the profile will be encrypted
|
||||
/// Returns a nullptr on error, for example if the profile already exists
|
||||
static Profile* createProfile(QString name, QString password);
|
||||
~Profile();
|
||||
|
||||
Core* getCore();
|
||||
QString getName() const;
|
||||
|
||||
void startCore(); ///< Starts the Core thread
|
||||
void restartCore(); ///< Delete core and restart a new one
|
||||
void startCore();
|
||||
void restartCore();
|
||||
bool isNewProfile();
|
||||
bool isEncrypted() const; ///< Returns true if we have a password set (doesn't check the actual file on disk)
|
||||
bool checkPassword(); ///< Checks whether the password is valid
|
||||
bool isEncrypted() const;
|
||||
bool checkPassword();
|
||||
QString getPassword() const;
|
||||
void setPassword(const QString &newPassword); ///< Changes the encryption password and re-saves everything with it
|
||||
void setPassword(const QString &newPassword);
|
||||
const TOX_PASS_KEY& getPasskey() const;
|
||||
|
||||
QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted
|
||||
void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles.
|
||||
void saveToxSave(QByteArray data); ///< Write the .tox save, encrypted if needed. Invalid on deleted profiles.
|
||||
QByteArray loadToxSave();
|
||||
void saveToxSave();
|
||||
void saveToxSave(QByteArray data);
|
||||
|
||||
QPixmap loadAvatar(); ///< Get our avatar from cache
|
||||
QPixmap loadAvatar(const QString& ownerId); ///< Get a contact's avatar from cache
|
||||
QByteArray loadAvatarData(const QString& ownerId); ///< Get a contact's avatar from cache
|
||||
QByteArray loadAvatarData(const QString& ownerId, const QString& password); ///< Get a contact's avatar from cache, with a specified profile password.
|
||||
void saveAvatar(QByteArray pic, const QString& ownerId); ///< Save an avatar to cache
|
||||
QByteArray getAvatarHash(const QString& ownerId); ///< Get the tox hash of a cached avatar
|
||||
void removeAvatar(const QString& ownerId); ///< Removes a cached avatar
|
||||
void removeAvatar(); ///< Removes our own avatar
|
||||
QPixmap loadAvatar();
|
||||
QPixmap loadAvatar(const QString& ownerId);
|
||||
QByteArray loadAvatarData(const QString& ownerId);
|
||||
QByteArray loadAvatarData(const QString& ownerId, const QString& password);
|
||||
void saveAvatar(QByteArray pic, const QString& ownerId);
|
||||
QByteArray getAvatarHash(const QString& ownerId);
|
||||
void removeAvatar(const QString& ownerId);
|
||||
void removeAvatar();
|
||||
|
||||
/// Returns true if the history is enabled in the settings, and loaded successfully for this profile
|
||||
bool isHistoryEnabled();
|
||||
/// May return a nullptr if the history failed to load
|
||||
History* getHistory();
|
||||
|
||||
/// Removes the profile permanently
|
||||
/// It is invalid to call loadToxSave or saveToxSave on a deleted profile
|
||||
/// Updates the profiles vector
|
||||
/// Returns a vector of filenames that could not be removed.
|
||||
QVector<QString> remove();
|
||||
|
||||
/// Tries to rename the profile
|
||||
bool rename(QString newName);
|
||||
|
||||
/// Scan for profile, automatically importing them if needed
|
||||
/// NOT thread-safe
|
||||
static void scanProfiles();
|
||||
static QVector<QString> getProfiles();
|
||||
|
||||
static bool exists(QString name);
|
||||
static bool isEncrypted(QString name); ///< Returns false on error. Checks the actual file on disk.
|
||||
static bool isEncrypted(QString name);
|
||||
|
||||
private:
|
||||
Profile(QString name, const QString &password, bool newProfile);
|
||||
/// Lists all the files in the config dir with a given extension
|
||||
/// Pass the raw extension, e.g. "jpeg" not ".jpeg".
|
||||
static QVector<QString> getFilesByExt(QString extension);
|
||||
/// Creates a .ini file for the given .tox profile
|
||||
/// Only pass the basename, without extension
|
||||
static void importProfile(QString name);
|
||||
/// Gets the path of the avatar file cached by this profile and corresponding to this owner ID
|
||||
/// If forceUnencrypted, we return the path to the plaintext file even if we're an encrypted profile
|
||||
QString avatarPath(const QString& ownerId, bool forceUnencrypted = false);
|
||||
|
||||
private:
|
||||
|
@ -110,11 +88,9 @@ private:
|
|||
QString name, password;
|
||||
TOX_PASS_KEY passkey;
|
||||
std::unique_ptr<History> history;
|
||||
bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet.
|
||||
bool isRemoved; ///< True if the profile has been removed by remove()
|
||||
bool newProfile;
|
||||
bool isRemoved;
|
||||
static QVector<QString> profiles;
|
||||
/// How much data we need to read to check if the file is encrypted
|
||||
/// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined
|
||||
static constexpr int encryptHeaderSize = 8;
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,14 @@
|
|||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
@class ProfileLocker
|
||||
@brief Locks a Tox profile so that multiple instances can not use the same profile.
|
||||
Only one lock can be acquired at the same time, which means
|
||||
that there is little need for manually unlocking.
|
||||
The current lock will expire if you exit or acquire a new one.
|
||||
*/
|
||||
|
||||
using namespace std;
|
||||
|
||||
unique_ptr<QLockFile> ProfileLocker::lockfile;
|
||||
|
@ -33,6 +41,14 @@ QString ProfileLocker::lockPathFromName(const QString& name)
|
|||
return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock";
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if a profile is currently locked by *another* instance.
|
||||
If we own the lock, we consider it lockable.
|
||||
There is no guarantee that the result will still be valid by the
|
||||
time it is returned, this is provided on a best effort basis.
|
||||
@param profile Profile name to check.
|
||||
@return True, if profile locked, false otherwise.
|
||||
*/
|
||||
bool ProfileLocker::isLockable(QString profile)
|
||||
{
|
||||
// If we already have the lock, it's definitely lockable
|
||||
|
@ -43,6 +59,11 @@ bool ProfileLocker::isLockable(QString profile)
|
|||
return newLock.tryLock();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Tries to acquire the lock on a profile, will not block.
|
||||
@param profile Profile to lock.
|
||||
@return Returns true if we already own the lock.
|
||||
*/
|
||||
bool ProfileLocker::lock(QString profile)
|
||||
{
|
||||
if (lockfile && curLockName == profile)
|
||||
|
@ -62,16 +83,26 @@ bool ProfileLocker::lock(QString profile)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Releases the lock on the current profile.
|
||||
*/
|
||||
void ProfileLocker::unlock()
|
||||
{
|
||||
if (!lockfile)
|
||||
return;
|
||||
|
||||
lockfile->unlock();
|
||||
delete lockfile.release();
|
||||
lockfile = nullptr;
|
||||
curLockName.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Check that we actually own the lock.
|
||||
In case the file was deleted on disk, restore it.
|
||||
If we can't get a lock, exit qTox immediately.
|
||||
If we never had a lock in the first place, exit immediately.
|
||||
*/
|
||||
void ProfileLocker::assertLock()
|
||||
{
|
||||
if (!lockfile)
|
||||
|
@ -96,17 +127,28 @@ void ProfileLocker::assertLock()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Print an error then exit immediately.
|
||||
*/
|
||||
void ProfileLocker::deathByBrokenLock()
|
||||
{
|
||||
qCritical() << "Lock is *BROKEN*, exiting immediately";
|
||||
abort();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Chacks, that profile locked.
|
||||
@return Returns true if we're currently holding a lock.
|
||||
*/
|
||||
bool ProfileLocker::hasLock()
|
||||
{
|
||||
return lockfile.operator bool();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get current locked profile name.
|
||||
@return Return the name of the currently loaded profile, a null string if there is none.
|
||||
*/
|
||||
QString ProfileLocker::getCurLockName()
|
||||
{
|
||||
if (lockfile)
|
||||
|
|
|
@ -24,39 +24,22 @@
|
|||
#include <QLockFile>
|
||||
#include <memory>
|
||||
|
||||
/// Locks a Tox profile so that multiple instances can not use the same profile.
|
||||
/// Only one lock can be acquired at the same time, which means
|
||||
/// that there is little need for manually unlocking.
|
||||
/// The current lock will expire if you exit or acquire a new one.
|
||||
class ProfileLocker
|
||||
{
|
||||
private:
|
||||
ProfileLocker()=delete;
|
||||
|
||||
public:
|
||||
/// Checks if a profile is currently locked by *another* instance
|
||||
/// If we own the lock, we consider it lockable
|
||||
/// There is no guarantee that the result will still be valid by the
|
||||
/// time it is returned, this is provided on a best effort basis
|
||||
static bool isLockable(QString profile);
|
||||
/// Tries to acquire the lock on a profile, will not block
|
||||
/// Returns true if we already own the lock
|
||||
static bool lock(QString profile);
|
||||
/// Releases the lock on the current profile
|
||||
static void unlock();
|
||||
/// Returns true if we're currently holding a lock
|
||||
static bool hasLock();
|
||||
/// Return the name of the currently loaded profile, a null string if there is none
|
||||
static QString getCurLockName();
|
||||
/// Check that we actually own the lock
|
||||
/// In case the file was deleted on disk, restore it
|
||||
/// If we can't get a lock, exit qTox immediately
|
||||
/// If we never had a lock in the firt place, exit immediately
|
||||
static void assertLock();
|
||||
|
||||
private:
|
||||
static QString lockPathFromName(const QString& name);
|
||||
static void deathByBrokenLock(); ///< Print an error then exit immediately
|
||||
static void deathByBrokenLock();
|
||||
|
||||
private:
|
||||
static std::unique_ptr<QLockFile> lockfile;
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "src/persistence/serialize.h"
|
||||
|
||||
/**
|
||||
@file serialize.cpp
|
||||
Most of functions in this file are unsafe unless otherwise specified.
|
||||
@warning Do not use them on untrusted data (e.g. check a signature first).
|
||||
*/
|
||||
|
||||
QByteArray doubleToData(double num)
|
||||
{
|
||||
union
|
||||
|
|
|
@ -25,9 +25,6 @@
|
|||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
/// Most of those functions are unsafe unless otherwise specified
|
||||
/// Do not use them on untrusted data (e.g. check a signature first)
|
||||
|
||||
QByteArray doubleToData(double num);
|
||||
QByteArray floatToData(float num);
|
||||
float dataToFloat(QByteArray data);
|
||||
|
|
|
@ -49,6 +49,17 @@
|
|||
|
||||
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
|
||||
|
||||
/**
|
||||
@var QHash<QString, QByteArray> Settings::widgetSettings
|
||||
@brief Assume all widgets have unique names
|
||||
@warning Don't use it to save every single thing you want to save, use it
|
||||
for some general purpose widgets, such as MainWindows or Splitters,
|
||||
which have widget->saveX() and widget->loadX() methods.
|
||||
|
||||
@var QString Settings::toxmeInfo
|
||||
@brief Toxme info like name@server
|
||||
*/
|
||||
|
||||
const QString Settings::globalSettingsFile = "qtox.ini";
|
||||
Settings* Settings::settings{nullptr};
|
||||
QMutex Settings::bigLock{QMutex::Recursive};
|
||||
|
@ -73,6 +84,9 @@ Settings::~Settings()
|
|||
delete settingsThread;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
Settings& Settings::getInstance()
|
||||
{
|
||||
if (!settings)
|
||||
|
@ -379,6 +393,9 @@ void Settings::loadPersonal(Profile* profile)
|
|||
ps.endGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asynchronous, saves the global settings.
|
||||
*/
|
||||
void Settings::saveGlobal()
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
|
@ -498,11 +515,18 @@ void Settings::saveGlobal()
|
|||
s.endGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asynchronous, saves the current profile.
|
||||
*/
|
||||
void Settings::savePersonal()
|
||||
{
|
||||
savePersonal(Nexus::getProfile());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asynchronous, saves the profile.
|
||||
@param profile Profile to save.
|
||||
*/
|
||||
void Settings::savePersonal(Profile* profile)
|
||||
{
|
||||
if (!profile)
|
||||
|
@ -600,6 +624,10 @@ uint32_t Settings::makeProfileId(const QString& profile)
|
|||
return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3];
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get path to directory, where the settings files are stored.
|
||||
@return Path to settings directory, ends with a directory separator.
|
||||
*/
|
||||
QString Settings::getSettingsDirPath()
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
|
@ -619,6 +647,10 @@ QString Settings::getSettingsDirPath()
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get path to directory, where the application data are stored.
|
||||
@return Path to application data, ends with a directory separator.
|
||||
*/
|
||||
QString Settings::getAppDataDirPath()
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
|
@ -640,6 +672,10 @@ QString Settings::getAppDataDirPath()
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get path to directory, where the application cache are stored.
|
||||
@return Path to application cache, ends with a directory separator.
|
||||
*/
|
||||
QString Settings::getAppCacheDirPath()
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
|
@ -1846,6 +1882,11 @@ void Settings::setAutoLogin(bool state)
|
|||
autoLogin = state;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Write a default personal .ini settings file for a profile.
|
||||
@param basename Filename without extension to save settings.
|
||||
@example If basename is "profile", settings will be saved in profile.ini
|
||||
*/
|
||||
void Settings::createPersonal(QString basename)
|
||||
{
|
||||
QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini";
|
||||
|
@ -1862,6 +1903,9 @@ void Settings::createPersonal(QString basename)
|
|||
ps.endGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Creates a path to the settings dir, if it doesn't already exist
|
||||
*/
|
||||
void Settings::createSettingsDir()
|
||||
{
|
||||
QString dir = Settings::getSettingsDirPath();
|
||||
|
@ -1870,6 +1914,9 @@ void Settings::createSettingsDir()
|
|||
qCritical() << "Error while creating directory " << dir;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Waits for all asynchronous operations to complete
|
||||
*/
|
||||
void Settings::sync()
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
|
|
|
@ -44,15 +44,15 @@ class Settings : public QObject
|
|||
public:
|
||||
static Settings& getInstance();
|
||||
static void destroyInstance();
|
||||
QString getSettingsDirPath(); ///< The returned path ends with a directory separator
|
||||
QString getAppDataDirPath(); ///< The returned path ends with a directory separator
|
||||
QString getAppCacheDirPath(); ///< The returned path ends with a directory separator
|
||||
QString getSettingsDirPath();
|
||||
QString getAppDataDirPath();
|
||||
QString getAppCacheDirPath();
|
||||
|
||||
void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist
|
||||
void createPersonal(QString basename); ///< Write a default personal .ini settings file for a profile
|
||||
void createSettingsDir();
|
||||
void createPersonal(QString basename);
|
||||
|
||||
void savePersonal(); ///< Asynchronous, saves the current profile
|
||||
void savePersonal(Profile *profile); ///< Asynchronous
|
||||
void savePersonal();
|
||||
void savePersonal(Profile *profile);
|
||||
|
||||
void loadGlobal();
|
||||
void loadPersonal();
|
||||
|
@ -67,8 +67,8 @@ public:
|
|||
|
||||
|
||||
public slots:
|
||||
void saveGlobal(); ///< Asynchronous
|
||||
void sync(); ///< Waits for all asynchronous operations to complete
|
||||
void saveGlobal();
|
||||
void sync();
|
||||
|
||||
signals:
|
||||
void dhtServerListChanged();
|
||||
|
@ -76,7 +76,6 @@ signals:
|
|||
void emojiFontChanged();
|
||||
|
||||
public:
|
||||
// Getter/setters
|
||||
const QList<DhtServer>& getDhtServerList() const;
|
||||
void setDhtServerList(const QList<DhtServer>& newDhtServerList);
|
||||
|
||||
|
@ -329,10 +328,6 @@ public:
|
|||
void removeFriendRequest(int index);
|
||||
void readFriendRequest(int index);
|
||||
|
||||
// Assume all widgets have unique names
|
||||
// Don't use it to save every single thing you want to save, use it
|
||||
// for some general purpose widgets, such as MainWindows or Splitters,
|
||||
// which have widget->saveX() and widget->loadX() methods.
|
||||
QByteArray getWidgetData(const QString& uniqueName) const;
|
||||
void setWidgetData(const QString& uniqueName, const QByteArray& data);
|
||||
|
||||
|
@ -401,7 +396,7 @@ private:
|
|||
uint32_t currentProfileId;
|
||||
|
||||
// Toxme Info
|
||||
QString toxmeInfo; // name@server
|
||||
QString toxmeInfo;
|
||||
QString toxmeBio;
|
||||
bool toxmePriv;
|
||||
QString toxmePass;
|
||||
|
|
|
@ -28,8 +28,37 @@
|
|||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
/**
|
||||
@class SettingsSerializer
|
||||
@brief Serializes a QSettings's data in an (optionally) encrypted binary format.
|
||||
SettingsSerializer can detect regular .ini files and serialized ones,
|
||||
it will read both regular and serialized .ini, but only save in serialized format.
|
||||
The file is encrypted with the current profile's password, if any.
|
||||
The file is only written to disk if save() is called, the destructor does not save to disk
|
||||
All member functions are reentrant, but not thread safe.
|
||||
|
||||
@enum SettingsSerializer::RecordTag
|
||||
@var Value
|
||||
Followed by a QString key then a QVariant value
|
||||
@var GroupStart
|
||||
Followed by a QString group name
|
||||
@var ArrayStart
|
||||
Followed by a QString array name and a vuint array size
|
||||
@var ArrayValue
|
||||
Followed by a vuint array index, a QString key then a QVariant value
|
||||
@var ArrayEnd
|
||||
Not followed by any data
|
||||
*/
|
||||
enum class RecordTag : uint8_t
|
||||
{
|
||||
|
||||
};
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
@var static const char magic[];
|
||||
@brief Little endian ASCII "QTOX" magic
|
||||
*/
|
||||
const char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58};
|
||||
|
||||
QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
|
||||
|
@ -199,6 +228,11 @@ SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key)
|
|||
return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if the file is serialized settings.
|
||||
@param filePath Path to file to check.
|
||||
@return False on error, true otherwise.
|
||||
*/
|
||||
bool SettingsSerializer::isSerializedFormat(QString filePath)
|
||||
{
|
||||
QFile f(filePath);
|
||||
|
@ -210,6 +244,9 @@ bool SettingsSerializer::isSerializedFormat(QString filePath)
|
|||
return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Loads the settings from file.
|
||||
*/
|
||||
void SettingsSerializer::load()
|
||||
{
|
||||
if (isSerializedFormat(path))
|
||||
|
@ -218,6 +255,9 @@ void SettingsSerializer::load()
|
|||
readIni();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Saves the current settings back to file
|
||||
*/
|
||||
void SettingsSerializer::save()
|
||||
{
|
||||
QSaveFile f(path);
|
||||
|
@ -545,6 +585,11 @@ void SettingsSerializer::readIni()
|
|||
group = array = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Remove group.
|
||||
@note The group must be empty.
|
||||
@param group ID of group to remove.
|
||||
*/
|
||||
void SettingsSerializer::removeGroup(int group)
|
||||
{
|
||||
assert(group<groups.size());
|
||||
|
|
|
@ -25,21 +25,15 @@
|
|||
#include <QString>
|
||||
#include <QDataStream>
|
||||
|
||||
/// Serializes a QSettings's data in an (optionally) encrypted binary format
|
||||
/// SettingsSerializer can detect regular .ini files and serialized ones,
|
||||
/// it will read both regular and serialized .ini, but only save in serialized format.
|
||||
/// The file is encrypted with the current profile's password, if any.
|
||||
/// The file is only written to disk if save() is called, the destructor does not save to disk
|
||||
/// All member functions are reentrant, but not thread safe.
|
||||
class SettingsSerializer
|
||||
{
|
||||
public:
|
||||
SettingsSerializer(QString filePath, const QString &password=QString());
|
||||
|
||||
static bool isSerializedFormat(QString filePath); ///< Check if the file is serialized settings. False on error.
|
||||
static bool isSerializedFormat(QString filePath);
|
||||
|
||||
void load(); ///< Loads the settings from file
|
||||
void save(); ///< Saves the current settings back to file
|
||||
void load();
|
||||
void save();
|
||||
|
||||
void beginGroup(const QString &prefix);
|
||||
void endGroup();
|
||||
|
@ -55,15 +49,10 @@ public:
|
|||
private:
|
||||
enum class RecordTag : uint8_t
|
||||
{
|
||||
/// Followed by a QString key then a QVariant value
|
||||
Value=0,
|
||||
/// Followed by a QString group name
|
||||
GroupStart=1,
|
||||
/// Followed by a QString array name and a vuint array size
|
||||
ArrayStart=2,
|
||||
/// Followed by a vuint array index, a QString key then a QVariant value
|
||||
ArrayValue=3,
|
||||
/// Not followed by any data
|
||||
ArrayEnd=4,
|
||||
};
|
||||
friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag);
|
||||
|
@ -94,7 +83,7 @@ private:
|
|||
void readSerialized();
|
||||
void readIni();
|
||||
void removeValue(const QString& key);
|
||||
void removeGroup(int group); ///< The group must be empty
|
||||
void removeGroup(int group);
|
||||
void writePackedVariant(QDataStream& dataStream, const QVariant& v);
|
||||
|
||||
private:
|
||||
|
@ -104,7 +93,7 @@ private:
|
|||
QVector<QString> groups;
|
||||
QVector<Array> arrays;
|
||||
QVector<Value> values;
|
||||
static const char magic[]; ///< Little endian ASCII "QTOX" magic
|
||||
static const char magic[];
|
||||
};
|
||||
|
||||
#endif // SETTINGSSERIALIZER_H
|
||||
|
|
|
@ -35,6 +35,23 @@
|
|||
#include <QStringBuilder>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
/**
|
||||
@class SmileyPack
|
||||
@brief Maps emoticons to smileys.
|
||||
|
||||
@var QHash<QString, QString> SmileyPack::filenameTable
|
||||
@brief Matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
|
||||
|
||||
@var QHash<QString, QIcon> SmileyPack::iconCache
|
||||
@brief representation of a smiley ie. "happy.png" -> data
|
||||
|
||||
@var QList<QStringList> SmileyPack::emoticons
|
||||
@brief {{ ":)", ":-)" }, {":(", ...}, ... }
|
||||
|
||||
@var QString SmileyPack::path
|
||||
@brief directory containing the cfg and image files
|
||||
*/
|
||||
|
||||
SmileyPack::SmileyPack()
|
||||
{
|
||||
loadingMutex.lock();
|
||||
|
@ -42,6 +59,9 @@ SmileyPack::SmileyPack()
|
|||
connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
SmileyPack& SmileyPack::getInstance()
|
||||
{
|
||||
static SmileyPack smileyPack;
|
||||
|
@ -92,6 +112,12 @@ bool SmileyPack::isValid(const QString &filename)
|
|||
return QFile(filename).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Load smile pack
|
||||
@note The caller must lock loadingMutex and should run it in a thread
|
||||
@param filename Filename of smilepack.
|
||||
@return False if cannot open file, true otherwise.
|
||||
*/
|
||||
bool SmileyPack::load(const QString& filename)
|
||||
{
|
||||
// discard old data
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
":/smileys", "./smileys", "/usr/share/qtox/smileys", "/usr/share/emoticons", "~/.kde4/share/emoticons", "~/.kde/share/emoticons" \
|
||||
}
|
||||
|
||||
//maps emoticons to smileys
|
||||
class SmileyPack : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -54,14 +53,14 @@ private:
|
|||
SmileyPack(SmileyPack&) = delete;
|
||||
SmileyPack& operator=(const SmileyPack&) = delete;
|
||||
|
||||
bool load(const QString& filename); ///< The caller must lock loadingMutex and should run it in a thread
|
||||
bool load(const QString& filename);
|
||||
void cacheSmiley(const QString& name);
|
||||
QIcon getCachedSmiley(const QString& key);
|
||||
|
||||
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
|
||||
QHash<QString, QIcon> iconCache; // representation of a smiley ie. "happy.png" -> data
|
||||
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... }
|
||||
QString path; // directory containing the cfg and image files
|
||||
QHash<QString, QString> filenameTable;
|
||||
QHash<QString, QIcon> iconCache;
|
||||
QList<QStringList> emoticons;
|
||||
QString path;
|
||||
mutable QMutex loadingMutex;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,6 +33,12 @@ bool toxSaveEventHandler(const QByteArray& eventData)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Import new profile.
|
||||
@note Will wait until the core is ready first.
|
||||
@param path Path to .tox file.
|
||||
@return True if import success, false, otherwise.
|
||||
*/
|
||||
bool handleToxSave(const QString& path)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
|
@ -59,7 +65,7 @@ bool handleToxSave(const QString& path)
|
|||
return false;
|
||||
}
|
||||
|
||||
QString profilePath = Settings::getInstance().getSettingsDirPath()+profile+Core::TOX_EXT;
|
||||
QString profilePath = Settings::getInstance().getSettingsDirPath() + profile + Core::TOX_EXT;
|
||||
|
||||
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
|
||||
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
class QString;
|
||||
class QByteArray;
|
||||
|
||||
/// Will wait until the core is ready first
|
||||
bool handleToxSave(const QString& path);
|
||||
|
||||
// Internals
|
||||
|
|
|
@ -38,6 +38,28 @@ extern "C" {
|
|||
#include "src/platform/camera/avfoundation.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
@class CameraDevice
|
||||
|
||||
Maintains an FFmpeg context for open camera devices,
|
||||
takes care of sharing the context accross users and closing
|
||||
the camera device when not in use. The device can be opened
|
||||
recursively, and must then be closed recursively
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
@var const QString CameraDevice::devName
|
||||
@brief Short name of the device
|
||||
|
||||
@var AVFormatContext* CameraDevice::context
|
||||
@brief Context of the open device, must always be valid
|
||||
|
||||
@var std::atomic_int CameraDevice::refcount;
|
||||
@brief Number of times the device was opened
|
||||
*/
|
||||
|
||||
|
||||
QHash<QString, CameraDevice*> CameraDevice::openDevices;
|
||||
QMutex CameraDevice::openDeviceLock, CameraDevice::iformatLock;
|
||||
AVInputFormat* CameraDevice::iformat{nullptr};
|
||||
|
@ -103,6 +125,18 @@ out:
|
|||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Opens a device.
|
||||
|
||||
Opens a device, creating a new one if needed
|
||||
If the device is alreay open in another mode, the mode
|
||||
will be ignored and the existing device is used
|
||||
If the mode does not exist, a new device can't be opened.
|
||||
|
||||
@param devName Device name to open.
|
||||
@param mode Mode of device to open.
|
||||
@return CameraDevice if the device could be opened, nullptr otherwise.
|
||||
*/
|
||||
CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
|
||||
{
|
||||
if (!getDefaultInputFormat())
|
||||
|
@ -205,11 +239,20 @@ CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
|
|||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Opens the device again. Never fails
|
||||
*/
|
||||
void CameraDevice::open()
|
||||
{
|
||||
++refcount;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Closes the device. Never fails.
|
||||
@note If returns true, "this" becomes invalid.
|
||||
@return True, if device finally deleted (closed last reference),
|
||||
false otherwise (if other references exist).
|
||||
*/
|
||||
bool CameraDevice::close()
|
||||
{
|
||||
if (--refcount > 0)
|
||||
|
@ -223,6 +266,11 @@ bool CameraDevice::close()
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get raw device list
|
||||
@note Uses avdevice_list_devices
|
||||
@return Raw device list
|
||||
*/
|
||||
QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
|
||||
{
|
||||
QVector<QPair<QString, QString>> devices;
|
||||
|
@ -234,11 +282,13 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
|
|||
AVFormatContext *s;
|
||||
if (!(s = avformat_alloc_context()))
|
||||
return devices;
|
||||
|
||||
if (!iformat->priv_class || !AV_IS_INPUT_DEVICE(iformat->priv_class->category))
|
||||
{
|
||||
avformat_free_context(s);
|
||||
return devices;
|
||||
}
|
||||
|
||||
s->iformat = iformat;
|
||||
if (s->iformat->priv_data_size > 0)
|
||||
{
|
||||
|
@ -280,7 +330,7 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
|
|||
|
||||
// Convert the list to a QVector
|
||||
devices.resize(devlist->nb_devices);
|
||||
for (int i=0; i<devlist->nb_devices; i++)
|
||||
for (int i = 0; i < devlist->nb_devices; i++)
|
||||
{
|
||||
AVDeviceInfo* dev = devlist->devices[i];
|
||||
devices[i].first = dev->device_name;
|
||||
|
@ -290,6 +340,11 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
|
|||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get device list with desciption
|
||||
@return A list of device names and descriptions.
|
||||
The names are the first part of the pair and can be passed to open(QString).
|
||||
*/
|
||||
QVector<QPair<QString, QString>> CameraDevice::getDeviceList()
|
||||
{
|
||||
QVector<QPair<QString, QString>> devices;
|
||||
|
@ -336,6 +391,11 @@ QVector<QPair<QString, QString>> CameraDevice::getDeviceList()
|
|||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the default device name.
|
||||
@return The short name of the default device
|
||||
This is either the device in the settings or the system default.
|
||||
*/
|
||||
QString CameraDevice::getDefaultDeviceName()
|
||||
{
|
||||
QString defaultdev = Settings::getInstance().getVideoDev();
|
||||
|
@ -354,11 +414,20 @@ QString CameraDevice::getDefaultDeviceName()
|
|||
return devlist[0].first;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Checks if a device name specifies a display.
|
||||
@param devName Device name to check.
|
||||
@return True, if device is screen, false otherwise.
|
||||
*/
|
||||
bool CameraDevice::isScreen(const QString &devName)
|
||||
{
|
||||
return devName.startsWith("x11grab") || devName.startsWith("gdigrab");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get list of resolutions and position of screens
|
||||
@return Vector of avaliable screen modes with offset
|
||||
*/
|
||||
QVector<VideoMode> CameraDevice::getScreenModes()
|
||||
{
|
||||
QList<QScreen*> screens = QApplication::screens();
|
||||
|
@ -376,6 +445,11 @@ QVector<VideoMode> CameraDevice::getScreenModes()
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the list of video modes for a device.
|
||||
@param devName Device name to get nodes from.
|
||||
@return Vector of available modes for the device.
|
||||
*/
|
||||
QVector<VideoMode> CameraDevice::getVideoModes(QString devName)
|
||||
{
|
||||
Q_UNUSED(devName);
|
||||
|
@ -401,6 +475,11 @@ QVector<VideoMode> CameraDevice::getVideoModes(QString devName)
|
|||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the name of the pixel format of a video mode.
|
||||
@param pixel_format Pixel format to get the name from.
|
||||
@return Name of the pixel format.
|
||||
*/
|
||||
QString CameraDevice::getPixelFormatString(uint32_t pixel_format)
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -410,6 +489,13 @@ QString CameraDevice::getPixelFormatString(uint32_t pixel_format)
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Compare two pixel formats.
|
||||
@param a First pixel format to compare.
|
||||
@param b Second pixel format to compare.
|
||||
@return True if we prefer format a to b,
|
||||
false otherwise (such as if there's no preference).
|
||||
*/
|
||||
bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b)
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -419,6 +505,10 @@ bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b)
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Sets CameraDevice::iformat to default.
|
||||
@return True if success, false if failure.
|
||||
*/
|
||||
bool CameraDevice::getDefaultInputFormat()
|
||||
{
|
||||
QMutexLocker locker(&iformatLock);
|
||||
|
|
|
@ -33,55 +33,36 @@ struct AVInputFormat;
|
|||
struct AVDeviceInfoList;
|
||||
struct AVDictionary;
|
||||
|
||||
/// Maintains an FFmpeg context for open camera devices,
|
||||
/// takes care of sharing the context accross users
|
||||
/// and closing the camera device when not in use.
|
||||
/// The device can be opened recursively,
|
||||
/// and must then be closed recursively
|
||||
class CameraDevice
|
||||
{
|
||||
public:
|
||||
/// Opens a device, creating a new one if needed
|
||||
/// If the device is alreay open in another mode, the mode
|
||||
/// will be ignored and the existing device is used
|
||||
/// If the mode does not exist, a new device can't be opened
|
||||
/// Returns a nullptr if the device couldn't be opened
|
||||
static CameraDevice* open(QString devName, VideoMode mode = VideoMode());
|
||||
void open(); ///< Opens the device again. Never fails
|
||||
bool close(); ///< Closes the device. Never fails. If returns true, "this" becomes invalid
|
||||
void open();
|
||||
bool close();
|
||||
|
||||
/// Returns a list of device names and descriptions
|
||||
/// The names are the first part of the pair and can be passed to open(QString)
|
||||
static QVector<QPair<QString, QString>> getDeviceList();
|
||||
|
||||
/// Get the list of video modes for a device
|
||||
static QVector<VideoMode> getVideoModes(QString devName);
|
||||
/// Get the name of the pixel format of a video mode
|
||||
static QString getPixelFormatString(uint32_t pixel_format);
|
||||
/// Returns true if we prefer format a to b, false otherwise (such as if there's no preference)
|
||||
static bool betterPixelFormat(uint32_t a, uint32_t b);
|
||||
|
||||
/// Returns the short name of the default defice
|
||||
/// This is either the device in the settings
|
||||
/// or the system default.
|
||||
static QString getDefaultDeviceName();
|
||||
|
||||
/// Checks if a device name specifies a display
|
||||
static bool isScreen(const QString &devName);
|
||||
|
||||
private:
|
||||
CameraDevice(const QString &devName, AVFormatContext *context);
|
||||
static CameraDevice* open(QString devName, AVDictionary** options);
|
||||
static bool getDefaultInputFormat(); ///< Sets CameraDevice::iformat, returns success/failure
|
||||
static QVector<QPair<QString, QString> > getRawDeviceListGeneric(); ///< Uses avdevice_list_devices
|
||||
static QVector<VideoMode> getScreenModes(); ///< Returns avaliable screen modes with offset
|
||||
static bool getDefaultInputFormat();
|
||||
static QVector<QPair<QString, QString> > getRawDeviceListGeneric();
|
||||
static QVector<VideoMode> getScreenModes();
|
||||
|
||||
public:
|
||||
const QString devName; ///< Short name of the device
|
||||
AVFormatContext* context; ///< Context of the open device, must always be valid
|
||||
const QString devName;
|
||||
AVFormatContext* context;
|
||||
|
||||
private:
|
||||
std::atomic_int refcount; ///< Number of times the device was opened
|
||||
std::atomic_int refcount;
|
||||
static QHash<QString, CameraDevice*> openDevices;
|
||||
static QMutex openDeviceLock, iformatLock;
|
||||
static AVInputFormat* iformat, *idesktopFormat;
|
||||
|
|
|
@ -33,6 +33,57 @@ extern "C" {
|
|||
#include "cameradevice.h"
|
||||
#include "videoframe.h"
|
||||
|
||||
/**
|
||||
@class CameraSource
|
||||
@brief This class is a wrapper to share a camera's captured video frames
|
||||
|
||||
It allows objects to suscribe and unsuscribe to the stream, starting
|
||||
the camera and streaming new video frames only when needed.
|
||||
This is a singleton, since we can only capture from one
|
||||
camera at the same time without thread-safety issues.
|
||||
The source is lazy in the sense that it will only keep the video
|
||||
device open as long as there are subscribers, the source can be
|
||||
open but the device closed if there are zero subscribers.
|
||||
*/
|
||||
|
||||
/**
|
||||
@var QVector<std::weak_ptr<VideoFrame>> CameraSource::freelist
|
||||
@brief Frames that need freeing before we can safely close the device
|
||||
|
||||
@var QFuture<void> CameraSource::streamFuture
|
||||
@brief Future of the streaming thread
|
||||
|
||||
@var QString CameraSource::deviceName
|
||||
@brief Short name of the device for CameraDevice's open(QString)
|
||||
|
||||
@var CameraDevice* CameraSource::device
|
||||
@brief Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences when becomes null.
|
||||
|
||||
@var VideoMode CameraSource::mode
|
||||
@brief What mode we tried to open the device in, all zeros means default mode
|
||||
|
||||
@var AVCodecContext* CameraSource::cctx
|
||||
@brief Codec context of the camera's selected video stream
|
||||
|
||||
@var AVCodecContext* CameraSource::cctxOrig
|
||||
@brief Codec context of the camera's selected video stream
|
||||
|
||||
@var int CameraSource::videoStreamIndex
|
||||
@brief A camera can have multiple streams, this is the one we're decoding
|
||||
|
||||
@var QMutex CameraSource::biglock
|
||||
@brief True when locked. Faster than mutexes for video decoding.
|
||||
|
||||
@var QMutex CameraSource::freelistLock
|
||||
@brief True when locked. Faster than mutexes for video decoding.
|
||||
|
||||
@var std::atomic_bool CameraSource::streamBlocker
|
||||
@brief Holds the streaming thread still when true
|
||||
|
||||
@var std::atomic_int CameraSource::subscriptions
|
||||
@brief Remember how many times we subscribed for RAII
|
||||
*/
|
||||
|
||||
CameraSource* CameraSource::instance{nullptr};
|
||||
|
||||
CameraSource::CameraSource()
|
||||
|
@ -45,6 +96,9 @@ CameraSource::CameraSource()
|
|||
avdevice_register_all();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
CameraSource& CameraSource::getInstance()
|
||||
{
|
||||
if (!instance)
|
||||
|
@ -61,6 +115,12 @@ void CameraSource::destroyInstance()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Opens the source for the camera device.
|
||||
@note If a device is already open, the source will seamlessly switch to the new device.
|
||||
|
||||
Opens the source for the camera device in argument, in the settings, or the system default.
|
||||
*/
|
||||
void CameraSource::open()
|
||||
{
|
||||
open(CameraDevice::getDefaultDeviceName());
|
||||
|
@ -103,6 +163,11 @@ void CameraSource::open(const QString& DeviceName, VideoMode Mode)
|
|||
streamBlocker = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Stops streaming.
|
||||
|
||||
Equivalent to opening the source with the video device "none".
|
||||
*/
|
||||
void CameraSource::close()
|
||||
{
|
||||
open("none");
|
||||
|
@ -214,6 +279,11 @@ void CameraSource::unsubscribe()
|
|||
subscriptions--;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Opens the video device and starts streaming.
|
||||
@note Callers must own the biglock.
|
||||
@return True if success, false otherwise.
|
||||
*/
|
||||
bool CameraSource::openDevice()
|
||||
{
|
||||
qDebug() << "Opening device " << deviceName;
|
||||
|
@ -296,6 +366,10 @@ bool CameraSource::openDevice()
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Closes the video device and stops streaming.
|
||||
@note Callers must own the biglock.
|
||||
*/
|
||||
void CameraSource::closeDevice()
|
||||
{
|
||||
qDebug() << "Closing device "<<deviceName;
|
||||
|
@ -324,6 +398,10 @@ void CameraSource::closeDevice()
|
|||
std::atomic_thread_fence(std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Blocking. Decodes video stream and emits new frames.
|
||||
@note Designed to run in its own thread.
|
||||
*/
|
||||
void CameraSource::stream()
|
||||
{
|
||||
auto streamLoop = [=]()
|
||||
|
@ -384,12 +462,28 @@ void CameraSource::stream()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CameraSource::freelistCallback
|
||||
@param freelistIndex
|
||||
|
||||
All VideoFrames must be deleted or released before we can close the device
|
||||
or the device will forcibly free them, and then ~VideoFrame() will double free.
|
||||
In theory very careful coding from our users could ensure all VideoFrames
|
||||
die before unsubscribing, even the ones currently in flight in the metatype system.
|
||||
But that's just asking for trouble and mysterious crashes, so we'll just
|
||||
maintain a freelist and have all VideoFrames tell us when they die so we can forget them.
|
||||
*/
|
||||
void CameraSource::freelistCallback(int freelistIndex)
|
||||
{
|
||||
QMutexLocker l{&freelistLock};
|
||||
freelist[freelistIndex].reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Get the index of a free slot in the freelist.
|
||||
@note Callers must hold the freelistLock.
|
||||
@return Index of a free slot.
|
||||
*/
|
||||
int CameraSource::getFreelistSlotLockless()
|
||||
{
|
||||
int size = freelist.size();
|
||||
|
|
|
@ -31,17 +31,6 @@
|
|||
class CameraDevice;
|
||||
struct AVCodecContext;
|
||||
|
||||
/**
|
||||
* This class is a wrapper to share a camera's captured video frames
|
||||
* It allows objects to suscribe and unsuscribe to the stream, starting
|
||||
* the camera and streaming new video frames only when needed.
|
||||
* This is a singleton, since we can only capture from one
|
||||
* camera at the same time without thread-safety issues.
|
||||
* The source is lazy in the sense that it will only keep the video
|
||||
* device open as long as there are subscribers, the source can be
|
||||
* open but the device closed if there are zero subscribers.
|
||||
**/
|
||||
|
||||
class CameraSource : public VideoSource
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -49,12 +38,10 @@ class CameraSource : public VideoSource
|
|||
public:
|
||||
static CameraSource& getInstance();
|
||||
static void destroyInstance();
|
||||
/// Opens the source for the camera device in argument, in the settings, or the system default
|
||||
/// If a device is already open, the source will seamlessly switch to the new device
|
||||
void open();
|
||||
void open(const QString& deviceName);
|
||||
void open(const QString& deviceName, VideoMode mode);
|
||||
void close(); ///< Equivalent to opening the source with the video device "none". Stops streaming.
|
||||
void close();
|
||||
bool isOpen();
|
||||
|
||||
// VideoSource interface
|
||||
|
@ -67,34 +54,24 @@ signals:
|
|||
private:
|
||||
CameraSource();
|
||||
~CameraSource();
|
||||
/// Blocking. Decodes video stream and emits new frames.
|
||||
/// Designed to run in its own thread.
|
||||
void stream();
|
||||
/// All VideoFrames must be deleted or released before we can close the device
|
||||
/// or the device will forcibly free them, and then ~VideoFrame() will double free.
|
||||
/// In theory very careful coding from our users could ensure all VideoFrames
|
||||
/// die before unsubscribing, even the ones currently in flight in the metatype system.
|
||||
/// But that's just asking for trouble and mysterious crashes, so we'll just
|
||||
/// maintain a freelist and have all VideoFrames tell us when they die so we can forget them.
|
||||
void freelistCallback(int freelistIndex);
|
||||
/// Get the index of a free slot in the freelist
|
||||
/// Callers must hold the freelistLock
|
||||
int getFreelistSlotLockless();
|
||||
bool openDevice(); ///< Callers must own the biglock. Actually opens the video device and starts streaming.
|
||||
void closeDevice(); ///< Callers must own the biglock. Actually closes the video device and stops streaming.
|
||||
bool openDevice();
|
||||
void closeDevice();
|
||||
|
||||
private:
|
||||
QVector<std::weak_ptr<VideoFrame>> freelist; ///< Frames that need freeing before we can safely close the device
|
||||
QFuture<void> streamFuture; ///< Future of the streaming thread
|
||||
QString deviceName; ///< Short name of the device for CameraDevice's open(QString)
|
||||
CameraDevice* device; ///< Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences when becomes null.
|
||||
VideoMode mode; ///< What mode we tried to open the device in, all zeros means default mode
|
||||
AVCodecContext* cctx, *cctxOrig; ///< Codec context of the camera's selected video stream
|
||||
int videoStreamIndex; ///< A camera can have multiple streams, this is the one we're decoding
|
||||
QMutex biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
|
||||
QVector<std::weak_ptr<VideoFrame>> freelist;
|
||||
QFuture<void> streamFuture;
|
||||
QString deviceName;
|
||||
CameraDevice* device;
|
||||
VideoMode mode;
|
||||
AVCodecContext* cctx, *cctxOrig;
|
||||
int videoStreamIndex;
|
||||
QMutex biglock, freelistLock;
|
||||
std::atomic_bool _isOpen;
|
||||
std::atomic_bool streamBlocker; ///< Holds the streaming thread still when true
|
||||
std::atomic_int subscriptions; ///< Remember how many times we subscribed for RAII
|
||||
std::atomic_bool streamBlocker;
|
||||
std::atomic_int subscriptions;
|
||||
|
||||
static CameraSource* instance;
|
||||
};
|
||||
|
|
|
@ -25,12 +25,34 @@ extern "C" {
|
|||
#include "corevideosource.h"
|
||||
#include "videoframe.h"
|
||||
|
||||
/**
|
||||
@class CoreVideoSource
|
||||
@brief A VideoSource that emits frames received by Core.
|
||||
*/
|
||||
|
||||
/**
|
||||
@var std::atomic_int subscribers
|
||||
@brief Number of suscribers
|
||||
|
||||
@var std::atomic_bool deleteOnClose
|
||||
@brief If true, self-delete after the last suscriber is gone
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief CoreVideoSource constructor.
|
||||
@note Only CoreAV should create a CoreVideoSource since
|
||||
only CoreAV can push images to it.
|
||||
*/
|
||||
CoreVideoSource::CoreVideoSource()
|
||||
: subscribers{0}, deleteOnClose{false},
|
||||
stopped{false}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Makes a copy of the vpx_image_t and emits it as a new VideoFrame.
|
||||
@param vpxframe Frame to copy.
|
||||
*/
|
||||
void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
|
||||
{
|
||||
if (stopped)
|
||||
|
@ -108,12 +130,22 @@ void CoreVideoSource::unsubscribe()
|
|||
biglock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Setup delete on close
|
||||
@param If true, self-delete after the last suscriber is gone
|
||||
*/
|
||||
void CoreVideoSource::setDeleteOnClose(bool newstate)
|
||||
{
|
||||
QMutexLocker locker(&biglock);
|
||||
deleteOnClose = newstate;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Stopping the source.
|
||||
@see The callers in CoreAV for the rationale
|
||||
|
||||
Stopping the source will block any pushFrame calls from doing anything
|
||||
*/
|
||||
void CoreVideoSource::stopSource()
|
||||
{
|
||||
QMutexLocker locker(&biglock);
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include "videosource.h"
|
||||
#include <QMutex>
|
||||
|
||||
/// A VideoSource that emits frames received by Core
|
||||
class CoreVideoSource : public VideoSource
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -36,23 +35,17 @@ public:
|
|||
virtual void unsubscribe() override;
|
||||
|
||||
private:
|
||||
// Only CoreAV should create a CoreVideoSource since
|
||||
// only CoreAV can push images to it
|
||||
CoreVideoSource();
|
||||
|
||||
/// Makes a copy of the vpx_image_t and emits it as a new VideoFrame
|
||||
void pushFrame(const vpx_image_t *frame);
|
||||
/// If true, self-delete after the last suscriber is gone
|
||||
void setDeleteOnClose(bool newstate);
|
||||
|
||||
/// Stopping the source will block any pushFrame calls from doing anything
|
||||
/// See the callers in CoreAV for the rationale
|
||||
void stopSource();
|
||||
void restartSource();
|
||||
|
||||
private:
|
||||
std::atomic_int subscribers; ///< Number of suscribers
|
||||
std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone
|
||||
std::atomic_int subscribers;
|
||||
std::atomic_bool deleteOnClose;
|
||||
QMutex biglock;
|
||||
std::atomic_bool stopped;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ NetCamView::NetCamView(int friendId, QWidget* parent)
|
|||
: GenericNetCamView(parent)
|
||||
, selfFrame{nullptr}
|
||||
, friendId{friendId}
|
||||
, e(false)
|
||||
{
|
||||
QString id = FriendList::findFriend(friendId)->getToxId().toString();
|
||||
videoSurface = new VideoSurface(Nexus::getProfile()->loadAvatar(id), this);
|
||||
|
|
|
@ -55,7 +55,7 @@ private:
|
|||
VideoSurface* selfVideoSurface;
|
||||
MovableWidget* selfFrame;
|
||||
int friendId;
|
||||
bool e = false;
|
||||
bool e;
|
||||
QVector<QMetaObject::Connection> connections;
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,17 @@ extern "C" {
|
|||
#include "videoframe.h"
|
||||
#include "camerasource.h"
|
||||
|
||||
/**
|
||||
@class VideoFrame
|
||||
|
||||
VideoFrame takes ownership of an AVFrame* and allows fast conversions to other formats
|
||||
Ownership of all video frame buffers is kept by the VideoFrame, even after conversion
|
||||
All references to the frame data become invalid when the VideoFrame is deleted
|
||||
We try to avoid pixel format conversions as much as possible, at the cost of some memory
|
||||
All methods are thread-safe. If provided freelistCallback will be called by the destructor,
|
||||
unless releaseFrame was called in between.
|
||||
*/
|
||||
|
||||
VideoFrame::VideoFrame(AVFrame* frame, int w, int h, int fmt, std::function<void()> freelistCallback)
|
||||
: freelistCallback{freelistCallback},
|
||||
frameOther{nullptr}, frameYUV420{nullptr}, frameRGB24{nullptr},
|
||||
|
@ -70,6 +81,10 @@ VideoFrame::VideoFrame(AVFrame* frame)
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief VideoFrame constructor. Disable copy.
|
||||
@note Use a shared_ptr if you need copies.
|
||||
*/
|
||||
VideoFrame::~VideoFrame()
|
||||
{
|
||||
if (freelistCallback)
|
||||
|
@ -78,6 +93,11 @@ VideoFrame::~VideoFrame()
|
|||
releaseFrameLockless();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Converts the VideoFrame to a QImage that shares our internal video buffer.
|
||||
@param size Size of resulting image.
|
||||
@return Converted image to RGB24 color model.
|
||||
*/
|
||||
QImage VideoFrame::toQImage(QSize size)
|
||||
{
|
||||
if (!convertToRGB24(size))
|
||||
|
@ -88,6 +108,11 @@ QImage VideoFrame::toQImage(QSize size)
|
|||
return QImage(*frameRGB24->data, frameRGB24->width, frameRGB24->height, *frameRGB24->linesize, QImage::Format_RGB888);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Converts the VideoFrame to a vpx_image_t.
|
||||
Converts the VideoFrame to a vpx_image_t that shares our internal video buffer.
|
||||
@return Converted image to vpx_image format.
|
||||
*/
|
||||
vpx_image *VideoFrame::toVpxImage()
|
||||
{
|
||||
vpx_image* img = vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, width, height, 0);
|
||||
|
@ -240,6 +265,12 @@ bool VideoFrame::convertToYUV420()
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Frees all frame memory.
|
||||
|
||||
Frees all internal buffers and frame data, removes the freelistCallback
|
||||
This makes all converted objects that shares our internal buffers invalid.
|
||||
*/
|
||||
void VideoFrame::releaseFrame()
|
||||
{
|
||||
QMutexLocker locker(&biglock);
|
||||
|
@ -269,6 +300,10 @@ void VideoFrame::releaseFrameLockless()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Return the size of the original frame
|
||||
@return The size of the original frame
|
||||
*/
|
||||
QSize VideoFrame::getSize()
|
||||
{
|
||||
return {width, height};
|
||||
|
|
|
@ -28,12 +28,6 @@ struct AVFrame;
|
|||
struct AVCodecContext;
|
||||
struct vpx_image;
|
||||
|
||||
/// VideoFrame takes ownership of an AVFrame* and allows fast conversions to other formats
|
||||
/// Ownership of all video frame buffers is kept by the VideoFrame, even after conversion
|
||||
/// All references to the frame data become invalid when the VideoFrame is deleted
|
||||
/// We try to avoid pixel format conversions as much as possible, at the cost of some memory
|
||||
/// All methods are thread-safe. If provided freelistCallback will be called by the destructor,
|
||||
/// unless releaseFrame was called in between.
|
||||
class VideoFrame
|
||||
{
|
||||
public:
|
||||
|
@ -42,17 +36,11 @@ public:
|
|||
VideoFrame(AVFrame* frame, int w, int h, int fmt, std::function<void()> freelistCallback);
|
||||
~VideoFrame();
|
||||
|
||||
/// Return the size of the original frame
|
||||
QSize getSize();
|
||||
|
||||
/// Frees all internal buffers and frame data, removes the freelistCallback
|
||||
/// This makes all converted objects that shares our internal buffers invalid
|
||||
void releaseFrame();
|
||||
|
||||
/// Converts the VideoFrame to a QImage that shares our internal video buffer
|
||||
QImage toQImage(QSize size = QSize());
|
||||
/// Converts the VideoFrame to a vpx_image_t that shares our internal video buffer
|
||||
/// Free it with operator delete, NOT vpx_img_free
|
||||
vpx_image* toVpxImage();
|
||||
|
||||
protected:
|
||||
|
@ -61,7 +49,6 @@ protected:
|
|||
void releaseFrameLockless();
|
||||
|
||||
private:
|
||||
// Disable copy. Use a shared_ptr if you need copies.
|
||||
VideoFrame(const VideoFrame& other)=delete;
|
||||
VideoFrame& operator=(const VideoFrame& other)=delete;
|
||||
|
||||
|
|
75
src/video/videomode.cpp
Normal file
75
src/video/videomode.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright © 2015-2016 by The qTox Project
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
qTox is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "videomode.h"
|
||||
|
||||
/**
|
||||
@struct VideoMode
|
||||
@brief Describes a video mode supported by a device.
|
||||
|
||||
@var unsigned short VideoMode::width, VideoMode::height
|
||||
@brief Displayed video resolution (NOT frame resolution).
|
||||
|
||||
@var unsigned short VideoMode::x, VideoMode::y
|
||||
@brief Coordinates of upper-left corner.
|
||||
|
||||
@var float VideoMode::FPS
|
||||
@brief Frames per second supported by the device at this resolution
|
||||
*/
|
||||
|
||||
VideoMode::VideoMode(int width, int height, int x, int y, int FPS, int format) :
|
||||
width(width), height(height), x(x), y(y),
|
||||
FPS(FPS), pixel_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
VideoMode::VideoMode(QRect rect) :
|
||||
width(rect.width()), height(rect.height()),
|
||||
x(rect.x()), y(rect.y()),
|
||||
FPS(0), pixel_format(0)
|
||||
{
|
||||
}
|
||||
|
||||
QRect VideoMode::toRect() const
|
||||
{
|
||||
return QRect(x, y, width, height);
|
||||
}
|
||||
|
||||
bool VideoMode::operator==(const VideoMode &other) const
|
||||
{
|
||||
return width == other.width
|
||||
&& height == other.height
|
||||
&& x == other.x
|
||||
&& y == other.y
|
||||
&& FPS == other.FPS
|
||||
&& pixel_format == other.pixel_format;
|
||||
}
|
||||
|
||||
uint32_t VideoMode::norm(const VideoMode &other) const
|
||||
{
|
||||
return abs(this->width-other.width) + abs(this->height-other.height);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief All zeros means a default/unspecified mode
|
||||
*/
|
||||
VideoMode::operator bool() const
|
||||
{
|
||||
return width || height || FPS;
|
||||
}
|
|
@ -17,60 +17,29 @@
|
|||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef VIDEOMODE_H
|
||||
#define VIDEOMODE_H
|
||||
|
||||
#include <QRect>
|
||||
#include <cstdint>
|
||||
|
||||
/// Describes a video mode supported by a device
|
||||
struct VideoMode
|
||||
{
|
||||
unsigned short width, height; ///< Displayed video resolution (NOT frame resolution)
|
||||
unsigned short x, y; ///< Coordinates of upper-left corner
|
||||
float FPS; ///< Max frames per second supported by the device at this resolution
|
||||
unsigned short width, height;
|
||||
unsigned short x, y;
|
||||
float FPS;
|
||||
uint32_t pixel_format;
|
||||
|
||||
VideoMode(int width = 0, int height = 0, int x = 0, int y = 0,
|
||||
int FPS = 0, int format = 0) :
|
||||
width(width), height(height), x(x), y(y),
|
||||
FPS(FPS), pixel_format(format)
|
||||
{
|
||||
}
|
||||
int FPS = 0, int format = 0);
|
||||
|
||||
VideoMode(QRect rect) :
|
||||
width(rect.width()), height(rect.height()),
|
||||
x(rect.x()), y(rect.y()),
|
||||
FPS(0), pixel_format(0)
|
||||
{
|
||||
}
|
||||
VideoMode(QRect rect);
|
||||
|
||||
QRect toRect() const
|
||||
{
|
||||
return QRect(x, y, width, height);
|
||||
}
|
||||
QRect toRect() const;
|
||||
|
||||
/// All zeros means a default/unspecified mode
|
||||
operator bool() const
|
||||
{
|
||||
return width || height || FPS;
|
||||
}
|
||||
|
||||
bool operator==(const VideoMode& other) const
|
||||
{
|
||||
return width == other.width
|
||||
&& height == other.height
|
||||
&& x == other.x
|
||||
&& y == other.y
|
||||
&& FPS == other.FPS
|
||||
&& pixel_format == other.pixel_format;
|
||||
}
|
||||
|
||||
uint32_t norm(const VideoMode& other) const
|
||||
{
|
||||
return abs(this->width-other.width) + abs(this->height-other.height);
|
||||
}
|
||||
operator bool() const;
|
||||
bool operator==(const VideoMode& other) const;
|
||||
uint32_t norm(const VideoMode& other) const;
|
||||
};
|
||||
|
||||
#endif // VIDEOMODE_H
|
||||
|
|
|
@ -25,24 +25,37 @@
|
|||
|
||||
class VideoFrame;
|
||||
|
||||
/// An abstract source of video frames
|
||||
/// When it has at least one subscriber the source will emit new video frames
|
||||
/// Subscribing is recursive, multiple users can subscribe to the same VideoSource
|
||||
/**
|
||||
@brief An abstract source of video frames
|
||||
|
||||
When it has at least one subscriber the source will emit new video frames
|
||||
Subscribing is recursive, multiple users can subscribe to the same VideoSource
|
||||
*/
|
||||
class VideoSource : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~VideoSource() = default;
|
||||
/// If subscribe sucessfully opens the source, it will start emitting frameAvailable signals
|
||||
/**
|
||||
If subscribe sucessfully opens the source, it will start emitting frameAvailable signals.
|
||||
*/
|
||||
virtual bool subscribe() = 0;
|
||||
/// Stop emitting frameAvailable signals, and free associated resources if necessary
|
||||
/**
|
||||
Stop emitting frameAvailable signals, and free associated resources if necessary.
|
||||
*/
|
||||
virtual void unsubscribe() = 0;
|
||||
|
||||
signals:
|
||||
/**
|
||||
Emitted when new frame available to use.
|
||||
@param frame New frame.
|
||||
*/
|
||||
void frameAvailable(std::shared_ptr<VideoFrame> frame);
|
||||
/// Emitted when the source is stopped for an indefinite amount of time,
|
||||
/// but might restart sending frames again later
|
||||
/**
|
||||
Emitted when the source is stopped for an indefinite amount of time,
|
||||
but might restart sending frames again later
|
||||
*/
|
||||
void sourceStopped();
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
#include <QLabel>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
@var std::atomic_bool VideoSurface::frameLock
|
||||
@brief Fast lock for lastFrame.
|
||||
*/
|
||||
|
||||
float getSizeRatio(const QSize size)
|
||||
{
|
||||
return size.width() / static_cast<float>(size.height());
|
||||
|
@ -63,6 +68,13 @@ bool VideoSurface::isExpanding() const
|
|||
return expanding;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Update source.
|
||||
@note nullptr is a valid option.
|
||||
@param src source to set.
|
||||
|
||||
Unsubscribe from old source and subscribe to new.
|
||||
*/
|
||||
void VideoSurface::setSource(VideoSource *src)
|
||||
{
|
||||
if (source == src)
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
~VideoSurface();
|
||||
|
||||
bool isExpanding() const;
|
||||
void setSource(VideoSource* src); //NULL is a valid option
|
||||
void setSource(VideoSource* src);
|
||||
QRect getBoundingRect() const;
|
||||
float getRatio() const;
|
||||
void setAvatar(const QPixmap& pixmap);
|
||||
|
@ -65,7 +65,7 @@ private:
|
|||
QRect boundingRect;
|
||||
VideoSource* source;
|
||||
std::shared_ptr<VideoFrame> lastFrame;
|
||||
std::atomic_bool frameLock; ///< Fast lock for lastFrame
|
||||
std::atomic_bool frameLock;
|
||||
uint8_t hasSubscribed;
|
||||
QPixmap avatar;
|
||||
float ratio;
|
||||
|
|
|
@ -89,9 +89,8 @@ void AboutUser::onSelectDirClicked()
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief AboutUser::onAcceptedClicked When users clicks the bottom OK button,
|
||||
* save all settings
|
||||
*/
|
||||
@brief Called when user clicks the bottom OK button, save all settings
|
||||
*/
|
||||
void AboutUser::onAcceptedClicked()
|
||||
{
|
||||
ToxId toxId = ToxId(ui->publicKey->text());
|
||||
|
|
|
@ -41,6 +41,11 @@
|
|||
#include <QWindow>
|
||||
#include <QScrollArea>
|
||||
|
||||
/**
|
||||
@var QString AddFriendForm::lastUsername
|
||||
@brief Cached username so we can retranslate the invite message
|
||||
*/
|
||||
|
||||
AddFriendForm::AddFriendForm()
|
||||
{
|
||||
tabWidget = new QTabWidget();
|
||||
|
|
|
@ -85,7 +85,7 @@ private:
|
|||
QTextEdit message;
|
||||
QVBoxLayout layout, headLayout;
|
||||
QWidget *head, *main;
|
||||
QString lastUsername; // Cached username so we can retranslate the invite message
|
||||
QString lastUsername;
|
||||
QTabWidget* tabWidget;
|
||||
QVBoxLayout* requestsLayout;
|
||||
QList<QPushButton*> acceptButtons;
|
||||
|
|
|
@ -26,10 +26,13 @@
|
|||
#include <QMenu>
|
||||
#include "src/core/corestructs.h"
|
||||
#include "src/chatlog/chatmessage.h"
|
||||
#include "../../core/toxid.h"
|
||||
#include "src/core/toxid.h"
|
||||
|
||||
// Spacing in px inserted when the author of the last message changes
|
||||
#define AUTHOR_CHANGE_SPACING 5 // why the hell is this a thing? surely the different font is enough?
|
||||
/**
|
||||
Spacing in px inserted when the author of the last message changes
|
||||
@note Why the hell is this a thing? surely the different font is enough?
|
||||
*/
|
||||
#define AUTHOR_CHANGE_SPACING 5
|
||||
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
|
|
@ -37,6 +37,14 @@
|
|||
#include <QDragEnterEvent>
|
||||
#include <QtAlgorithms>
|
||||
|
||||
/**
|
||||
@var QList<QLabel*> GroupChatForm::peerLabels
|
||||
@brief Maps peernumbers to the QLabels in namesListLayout.
|
||||
|
||||
@var QMap<int, QTimer*> GroupChatForm::peerAudioTimers
|
||||
@brief Timeout = peer stopped sending audio.
|
||||
*/
|
||||
|
||||
GroupChatForm::GroupChatForm(Group* chatGroup)
|
||||
: group(chatGroup), inCall{false}
|
||||
{
|
||||
|
|
|
@ -61,8 +61,8 @@ private:
|
|||
|
||||
private:
|
||||
Group* group;
|
||||
QList<QLabel*> peerLabels; // maps peernumbers to the QLabels in namesListLayout
|
||||
QMap<int, QTimer*> peerAudioTimers; // timeout = peer stopped sending audio
|
||||
QList<QLabel*> peerLabels;
|
||||
QMap<int, QTimer*> peerAudioTimers;
|
||||
FlowLayout* namesListLayout;
|
||||
QLabel *nusersLabel;
|
||||
TabCompleter* tabber;
|
||||
|
|
|
@ -20,15 +20,21 @@
|
|||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* This file was taken from the Quassel IRC client source (src/uisupport), and
|
||||
was greatly simplified for use in qTox. */
|
||||
/**
|
||||
@file tabcompleter.h
|
||||
@file tabcompleter.cpp
|
||||
These files were taken from the Quassel IRC client source (src/uisupport), and
|
||||
was greatly simplified for use in qTox.
|
||||
*/
|
||||
|
||||
#include "tabcompleter.h"
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "src/core/core.h"
|
||||
#include "src/group.h"
|
||||
#include "src/widget/tool/chattextedit.h"
|
||||
#include <QRegExp>
|
||||
#include <QKeyEvent>
|
||||
|
||||
const QString TabCompleter::nickSuffix = QString(": ");
|
||||
|
||||
|
|
|
@ -20,18 +20,13 @@
|
|||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* This file was taken from the Quassel IRC client source (src/uisupport), and
|
||||
was greatly simplified for use in qTox. */
|
||||
|
||||
#ifndef TABCOMPLETER_H
|
||||
#define TABCOMPLETER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QObject> // I'm really confused why I need this
|
||||
|
||||
class ChatTextEdit;
|
||||
class Group;
|
||||
#include "src/group.h"
|
||||
#include "src/widget/tool/chattextedit.h"
|
||||
|
||||
class TabCompleter : public QObject
|
||||
{
|
||||
|
|
|
@ -31,6 +31,17 @@
|
|||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
|
||||
/**
|
||||
@class GUI
|
||||
@brief Abstracts the GUI from the target backend (DesktopGUI, ...)
|
||||
|
||||
All the functions exposed here are thread-safe.
|
||||
Prefer calling this class to calling a GUI backend directly.
|
||||
|
||||
@fn void GUI::resized()
|
||||
@brief Emitted when the GUI is resized on supported platforms.
|
||||
*/
|
||||
|
||||
GUI::GUI(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
@ -39,6 +50,9 @@ GUI::GUI(QObject *parent) :
|
|||
connect(Nexus::getDesktopGUI(), &Widget::resized, this, &GUI::resized);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns the singleton instance.
|
||||
*/
|
||||
GUI& GUI::getInstance()
|
||||
{
|
||||
static GUI gui;
|
||||
|
@ -47,6 +61,9 @@ GUI& GUI::getInstance()
|
|||
|
||||
// Implementation of the public clean interface
|
||||
|
||||
/**
|
||||
@brief Clear the GUI's contact list.
|
||||
*/
|
||||
void GUI::clearContacts()
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -55,6 +72,11 @@ void GUI::clearContacts()
|
|||
QMetaObject::invokeMethod(&getInstance(), "_clearContacts", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Will enable or disable the GUI.
|
||||
@note A disabled GUI can't be interacted with by the user.
|
||||
@param state Enable/disable GUI.
|
||||
*/
|
||||
void GUI::setEnabled(bool state)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -68,6 +90,12 @@ void GUI::setEnabled(bool state)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Change the title of the main window.
|
||||
@param title Titile to set.
|
||||
|
||||
This is usually always visible to the user.
|
||||
*/
|
||||
void GUI::setWindowTitle(const QString& title)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -81,6 +109,9 @@ void GUI::setWindowTitle(const QString& title)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Reloads the application theme and redraw the window.
|
||||
*/
|
||||
void GUI::reloadTheme()
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -93,6 +124,9 @@ void GUI::reloadTheme()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Optionally switches to a view of the qTox update being downloaded.
|
||||
*/
|
||||
void GUI::showUpdateDownloadProgress()
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -105,6 +139,11 @@ void GUI::showUpdateDownloadProgress()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Show some text to the user.
|
||||
@param title Title of information window.
|
||||
@param msg Text in information window.
|
||||
*/
|
||||
void GUI::showInfo(const QString& title, const QString& msg)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -118,6 +157,11 @@ void GUI::showInfo(const QString& title, const QString& msg)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Show a warning to the user
|
||||
@param title Title of warning window.
|
||||
@param msg Text in warning window.
|
||||
*/
|
||||
void GUI::showWarning(const QString& title, const QString& msg)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -131,6 +175,11 @@ void GUI::showWarning(const QString& title, const QString& msg)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Show an error to the user.
|
||||
@param title Title of error window.
|
||||
@param msg Text in error window.
|
||||
*/
|
||||
void GUI::showError(const QString& title, const QString& msg)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -149,6 +198,15 @@ void GUI::showError(const QString& title, const QString& msg)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asks the user a question with Ok/Cansel or Yes/No buttons.
|
||||
@param title Title of question window.
|
||||
@param msg Text in question window.
|
||||
@param defaultAns If is true, default was positive answer. Negative otherwise.
|
||||
@param warning If is true, we will use a special warning style.
|
||||
@param yesno Show "Yes" and "No" buttons.
|
||||
@return True if the answer is positive, false otherwise.
|
||||
*/
|
||||
bool GUI::askQuestion(const QString& title, const QString& msg,
|
||||
bool defaultAns, bool warning,
|
||||
bool yesno)
|
||||
|
@ -169,6 +227,18 @@ bool GUI::askQuestion(const QString& title, const QString& msg,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asks the user a question.
|
||||
|
||||
The text for the displayed buttons can be specified.
|
||||
@param title Title of question window.
|
||||
@param msg Text in question window.
|
||||
@param button1 Text of positive button.
|
||||
@param button2 Text of negative button.
|
||||
@param defaultAns If is true, default was positive answer. Negative otherwise.
|
||||
@param warning If is true, we will use a special warning style.
|
||||
@return True if the answer is positive, false otherwise.
|
||||
*/
|
||||
bool GUI::askQuestion(const QString& title, const QString& msg,
|
||||
const QString& button1, const QString& button2,
|
||||
bool defaultAns, bool warning)
|
||||
|
@ -188,6 +258,21 @@ bool GUI::askQuestion(const QString& title, const QString& msg,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asks the user to input text and returns the answer.
|
||||
|
||||
The interface is equivalent to QInputDialog::getItem()
|
||||
@param parent Is the dialog's parent widget
|
||||
@param title Is the text which is displayed in the title bar of the dialog.
|
||||
@param label Is the text which is shown to the user (it should say what should be entered).
|
||||
@param items Is the string list which is inserted into the combobox.
|
||||
@param current Is the number of the item which should be the current item.
|
||||
@param editable If is true the user can enter their own text, otherwise the user may only select one of the existing items.
|
||||
@param ok If is nonnull will be set to true if the user pressed OK and to false if the user pressed Cancel.
|
||||
@param flags The dialog will uses to widget flags.
|
||||
@param hints Is the input method hints that will be used if the combobox is editable and an input method is active.
|
||||
@return This function returns the text of the current item, or if editable is true, the current text of the combobox.
|
||||
*/
|
||||
QString GUI::itemInputDialog(QWidget * parent, const QString & title,
|
||||
const QString & label, const QStringList & items,
|
||||
int current, bool editable, bool * ok,
|
||||
|
@ -211,6 +296,12 @@ QString GUI::itemInputDialog(QWidget * parent, const QString & title,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Asks the user to answer a password.
|
||||
@param cancel Is the text on the cancel button.
|
||||
@param body Is descriptive text that will be shown to the user.
|
||||
@return Entered password.
|
||||
*/
|
||||
QString GUI::passwordDialog(const QString& cancel, const QString& body)
|
||||
{
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
|
@ -369,6 +460,10 @@ QString GUI::_passwordDialog(const QString& cancel, const QString& body)
|
|||
|
||||
// Other
|
||||
|
||||
/**
|
||||
@brief Get the main widget.
|
||||
@return The main QWidget* of the application
|
||||
*/
|
||||
QWidget* GUI::getMainWidget()
|
||||
{
|
||||
QWidget* maingui{nullptr};
|
||||
|
|
|
@ -25,69 +25,44 @@
|
|||
|
||||
class QWidget;
|
||||
|
||||
/// Abstracts the GUI from the target backend (DesktopGUI, ...)
|
||||
/// All the functions exposed here are thread-safe
|
||||
/// Prefer calling this class to calling a GUI backend directly
|
||||
class GUI : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static GUI& getInstance();
|
||||
/// Returns the main QWidget* of the application
|
||||
static QWidget* getMainWidget();
|
||||
/// Clear the GUI's contact list
|
||||
static void clearContacts();
|
||||
/// Will enable or disable the GUI.
|
||||
/// A disabled GUI can't be interacted with by the user
|
||||
static void setEnabled(bool state);
|
||||
/// Change the title of the main window
|
||||
/// This is usually always visible to the user
|
||||
static void setWindowTitle(const QString& title);
|
||||
/// Reloads the application theme and redraw the window
|
||||
static void reloadTheme();
|
||||
/// Optionally switches to a view of the qTox update being downloaded
|
||||
static void showUpdateDownloadProgress();
|
||||
/// Show some text to the user, for example in a message box
|
||||
static void showInfo(const QString& title, const QString& msg);
|
||||
/// Show a warning to the user, for example in a message box
|
||||
static void showWarning(const QString& title, const QString& msg);
|
||||
/// Show an error to the user, for example in a message box
|
||||
static void showError(const QString& title, const QString& msg);
|
||||
/// Asks the user a question, for example in a message box.
|
||||
/// If warning is true, we will use a special warning style.
|
||||
/// Returns the answer.
|
||||
static bool askQuestion(const QString& title, const QString& msg,
|
||||
bool defaultAns = false, bool warning = true,
|
||||
bool yesno = true);
|
||||
/// Asks the user a question, for example in a message box.
|
||||
/// The text for the displayed buttons can be specified.
|
||||
/// If warning is true, we will use a special warning style.
|
||||
/// Returns the answer.
|
||||
|
||||
static bool askQuestion(const QString& title, const QString& msg,
|
||||
const QString& button1, const QString& button2,
|
||||
bool defaultAns = false, bool warning = true);
|
||||
/// Asks the user to input text and returns the answer.
|
||||
/// The interface is equivalent to QInputDialog::getItem()
|
||||
|
||||
static QString itemInputDialog(QWidget * parent, const QString & title,
|
||||
const QString & label, const QStringList & items,
|
||||
int current = 0, bool editable = true, bool * ok = 0,
|
||||
Qt::WindowFlags flags = 0,
|
||||
Qt::InputMethodHints hints = Qt::ImhNone);
|
||||
/// Asks the user to answer a password
|
||||
/// cancel is the text on the cancel button and body
|
||||
/// is descriptive text that will be shown to the user
|
||||
|
||||
static QString passwordDialog(const QString& cancel, const QString& body);
|
||||
|
||||
signals:
|
||||
/// Emitted when the GUI is resized on supported platforms
|
||||
/// Guaranteed to work on desktop platforms
|
||||
void resized();
|
||||
|
||||
private:
|
||||
explicit GUI(QObject *parent = 0);
|
||||
|
||||
// Private implementation, those must be called from the GUI thread
|
||||
private slots:
|
||||
// Private implementation, those must be called from the GUI thread
|
||||
void _clearContacts();
|
||||
void _setEnabled(bool state);
|
||||
void _setWindowTitle(const QString& title);
|
||||
|
|
|
@ -71,6 +71,9 @@ LoginScreen::~LoginScreen()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Resets the UI, clears all fields.
|
||||
*/
|
||||
void LoginScreen::reset()
|
||||
{
|
||||
ui->newUsername->clear();
|
||||
|
|
|
@ -36,7 +36,7 @@ class LoginScreen : public QWidget
|
|||
public:
|
||||
explicit LoginScreen(QWidget *parent = 0);
|
||||
~LoginScreen();
|
||||
void reset(); ///< Resets the UI, clears all fields
|
||||
void reset();
|
||||
|
||||
bool event(QEvent* event) final override;
|
||||
|
||||
|
|
|
@ -20,8 +20,14 @@
|
|||
#include "maskablepixmapwidget.h"
|
||||
#include <QPainter>
|
||||
|
||||
/**
|
||||
@var QPixmap* MaskablePixmapWidget::renderTarget
|
||||
@brief pointer to dynamically call the constructor.
|
||||
*/
|
||||
|
||||
MaskablePixmapWidget::MaskablePixmapWidget(QWidget *parent, QSize size, QString maskName)
|
||||
: QWidget(parent)
|
||||
, renderTarget(nullptr)
|
||||
, maskName(maskName)
|
||||
, clickable(false)
|
||||
{
|
||||
|
|
|
@ -42,8 +42,8 @@ protected:
|
|||
virtual void mousePressEvent(QMouseEvent *) final override;
|
||||
|
||||
private:
|
||||
QPixmap pixmap, mask, unscaled; // a lot of memory...
|
||||
QPixmap* renderTarget = nullptr; // pointer to dynamically call the constructor
|
||||
QPixmap pixmap, mask, unscaled;
|
||||
QPixmap* renderTarget;
|
||||
QSize size;
|
||||
QString maskName;
|
||||
bool clickable;
|
||||
|
|
|
@ -67,16 +67,16 @@ void NotificationScrollArea::trackWidget(GenericChatroomWidget* widget)
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Delete notification bar to visible elements on scroll area
|
||||
*/
|
||||
@brief Delete notification bar from visible elements on scroll area
|
||||
*/
|
||||
void NotificationScrollArea::updateVisualTracking() {
|
||||
updateTracking(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delete notification bar from visible elements and widget on scroll area
|
||||
* @param widget Chatroom widget to remove from tracked widgets
|
||||
*/
|
||||
@brief Delete notification bar from visible elements and widget on scroll area
|
||||
@param widget Chatroom widget to remove from tracked widgets
|
||||
*/
|
||||
void NotificationScrollArea::updateTracking(GenericChatroomWidget *widget)
|
||||
{
|
||||
QHash<GenericChatroomWidget*, Visibility>::iterator i = trackedWidgets.begin();
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
#include <sys/errno.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
@file qrwidget.cpp
|
||||
@link https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c
|
||||
*/
|
||||
|
||||
QRWidget::QRWidget(QWidget *parent) : QWidget(parent), data("0")
|
||||
//Note: The encoding fails with empty string so I just default to something else.
|
||||
//Use the setQRData() call to change this.
|
||||
|
@ -58,10 +63,10 @@ QImage* QRWidget::getImage()
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief QRWidget::saveImage
|
||||
* @param path Full path to the file with extension.
|
||||
* @return indicate if saving was successful.
|
||||
*/
|
||||
@brief QRWidget::saveImage
|
||||
@param path Full path to the file with extension.
|
||||
@return indicate if saving was successful.
|
||||
*/
|
||||
bool QRWidget::saveImage(QString path)
|
||||
{
|
||||
return image->save(path, 0, 75); //0 - image format same as file extension, 75-quality, png file is ~6.3kb
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
#ifndef QRWIDGET_H
|
||||
#define QRWIDGET_H
|
||||
|
||||
// https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QRWidget : public QWidget
|
||||
|
|
|
@ -31,6 +31,31 @@
|
|||
#include <QSvgRenderer>
|
||||
#include <QPainter>
|
||||
|
||||
/**
|
||||
@enum Style::Font
|
||||
|
||||
@var ExtraBig
|
||||
@brief [SystemDefault + 2]px, bold
|
||||
|
||||
@var Big
|
||||
@brief [SystemDefault]px
|
||||
|
||||
@var BigBold
|
||||
@brief [SystemDefault]px, bold
|
||||
|
||||
@var Medium
|
||||
@brief [SystemDefault - 1]px
|
||||
|
||||
@var MediumBold
|
||||
@brief [SystemDefault - 1]px, bold
|
||||
|
||||
@var Small
|
||||
@brief [SystemDefault - 2]px
|
||||
|
||||
@var SmallLight
|
||||
@brief [SystemDefault - 2]px, light
|
||||
*/
|
||||
|
||||
// helper functions
|
||||
QFont appFont(int pixelSize, int weight)
|
||||
{
|
||||
|
@ -181,6 +206,12 @@ void Style::setThemeColor(int color)
|
|||
setThemeColor(themeColorColors[color]);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Set theme color.
|
||||
@param color Color to set.
|
||||
|
||||
Pass an invalid QColor to reset to defaults.
|
||||
*/
|
||||
void Style::setThemeColor(const QColor &color)
|
||||
{
|
||||
if (!color.isValid())
|
||||
|
@ -205,6 +236,9 @@ void Style::setThemeColor(const QColor &color)
|
|||
dict["@themeLight"] = getColor(ThemeLight).name();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Reloads some CCS
|
||||
*/
|
||||
void Style::applyTheme()
|
||||
{
|
||||
GUI::reloadTheme();
|
||||
|
|
|
@ -49,13 +49,13 @@ public:
|
|||
|
||||
enum Font
|
||||
{
|
||||
ExtraBig, // [SystemDefault + 2]px, bold
|
||||
Big, // [SystemDefault ]px
|
||||
BigBold, // [SystemDefault ]px, bold
|
||||
Medium, // [SystemDefault - 1]px
|
||||
MediumBold, // [SystemDefault - 1]px, bold
|
||||
Small, // [SystemDefault - 2]px
|
||||
SmallLight // [SystemDefault - 2]px, light
|
||||
ExtraBig,
|
||||
Big,
|
||||
BigBold,
|
||||
Medium,
|
||||
MediumBold,
|
||||
Small,
|
||||
SmallLight
|
||||
};
|
||||
|
||||
static QStringList getThemeColorNames();
|
||||
|
@ -65,8 +65,8 @@ public:
|
|||
static QString resolve(QString qss, const QFont& baseFont = QFont());
|
||||
static void repolish(QWidget* w);
|
||||
static void setThemeColor(int color);
|
||||
static void setThemeColor(const QColor &color); ///< Pass an invalid QColor to reset to defaults
|
||||
static void applyTheme(); ///< Reloads some CCS
|
||||
static void setThemeColor(const QColor &color);
|
||||
static void applyTheme();
|
||||
static QPixmap scaleSvgImage(const QString& path, uint32_t width, uint32_t height);
|
||||
|
||||
static QList<QColor> themeColorColors;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user