623 lines
16 KiB
C++
623 lines
16 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#pragma once
|
|
|
|
#include "core/basic_types.h"
|
|
#include "base/flags.h"
|
|
#include "base/algorithm.h"
|
|
|
|
// Define specializations for QByteArray for Qt 5.3.2, because
|
|
// QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype.
|
|
#ifdef OS_MAC_OLD
|
|
namespace gsl {
|
|
|
|
template <>
|
|
inline span<char> make_span<QByteArray>(QByteArray &cont) {
|
|
return span<char>(cont.data(), cont.size());
|
|
}
|
|
|
|
template <>
|
|
inline span<const char> make_span(const QByteArray &cont) {
|
|
return span<const char>(cont.constData(), cont.size());
|
|
}
|
|
|
|
} // namespace gsl
|
|
#endif // OS_MAC_OLD
|
|
|
|
namespace base {
|
|
|
|
template <typename D, typename T>
|
|
inline constexpr D up_cast(T object) {
|
|
using DV = std::decay_t<decltype(*D())>;
|
|
using TV = std::decay_t<decltype(*T())>;
|
|
if constexpr (std::is_base_of_v<DV, TV>) {
|
|
return object;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
template <typename Container, typename T>
|
|
inline bool contains(const Container &container, const T &value) {
|
|
auto end = std::end(container);
|
|
return std::find(std::begin(container), end, value) != end;
|
|
}
|
|
|
|
// We need a custom comparator for std::set<std::unique_ptr<T>>::find to work with pointers.
|
|
// thanks to http://stackoverflow.com/questions/18939882/raw-pointer-lookup-for-sets-of-unique-ptrs
|
|
template <typename T>
|
|
struct pointer_comparator {
|
|
using is_transparent = std::true_type;
|
|
|
|
// helper does some magic in order to reduce the number of
|
|
// pairs of types we need to know how to compare: it turns
|
|
// everything into a pointer, and then uses `std::less<T*>`
|
|
// to do the comparison:
|
|
struct helper {
|
|
T *ptr = nullptr;
|
|
helper() = default;
|
|
helper(const helper &other) = default;
|
|
helper(T *p) : ptr(p) {
|
|
}
|
|
template <typename ...Ts>
|
|
helper(const std::shared_ptr<Ts...> &other) : ptr(other.get()) {
|
|
}
|
|
template <typename ...Ts>
|
|
helper(const std::unique_ptr<Ts...> &other) : ptr(other.get()) {
|
|
}
|
|
bool operator<(helper other) const {
|
|
return std::less<T*>()(ptr, other.ptr);
|
|
}
|
|
};
|
|
|
|
// without helper, we'd need 2^n different overloads, where
|
|
// n is the number of types we want to support (so, 8 with
|
|
// raw pointers, unique pointers, and shared pointers). That
|
|
// seems silly.
|
|
// && helps enforce rvalue use only
|
|
bool operator()(const helper &&lhs, const helper &&rhs) const {
|
|
return lhs < rhs;
|
|
}
|
|
|
|
};
|
|
|
|
template <typename T>
|
|
using set_of_unique_ptr = std::set<std::unique_ptr<T>, base::pointer_comparator<T>>;
|
|
|
|
template <typename T>
|
|
using set_of_shared_ptr = std::set<std::shared_ptr<T>, base::pointer_comparator<T>>;
|
|
|
|
using byte_span = gsl::span<gsl::byte>;
|
|
using const_byte_span = gsl::span<const gsl::byte>;
|
|
using byte_vector = std::vector<gsl::byte>;
|
|
template <size_t N>
|
|
using byte_array = std::array<gsl::byte, N>;
|
|
|
|
inline void copy_bytes(byte_span destination, const_byte_span source) {
|
|
Expects(destination.size() >= source.size());
|
|
memcpy(destination.data(), source.data(), source.size());
|
|
}
|
|
|
|
inline void move_bytes(byte_span destination, const_byte_span source) {
|
|
Expects(destination.size() >= source.size());
|
|
memmove(destination.data(), source.data(), source.size());
|
|
}
|
|
|
|
inline void set_bytes(byte_span destination, gsl::byte value) {
|
|
memset(destination.data(), gsl::to_integer<unsigned char>(value), destination.size());
|
|
}
|
|
|
|
inline int compare_bytes(const_byte_span a, const_byte_span b) {
|
|
auto aSize = a.size(), bSize = b.size();
|
|
return (aSize > bSize) ? 1 : (aSize < bSize) ? -1 : memcmp(a.data(), b.data(), aSize);
|
|
}
|
|
|
|
// Thanks https://stackoverflow.com/a/28139075
|
|
|
|
template <typename Container>
|
|
struct reversion_wrapper {
|
|
Container &container;
|
|
};
|
|
|
|
template <typename Container>
|
|
auto begin(reversion_wrapper<Container> wrapper) {
|
|
return std::rbegin(wrapper.container);
|
|
}
|
|
|
|
template <typename Container>
|
|
auto end(reversion_wrapper<Container> wrapper) {
|
|
return std::rend(wrapper.container);
|
|
}
|
|
|
|
template <typename Container>
|
|
reversion_wrapper<Container> reversed(Container &&container) {
|
|
return { container };
|
|
}
|
|
|
|
template <typename Value, typename From, typename Till>
|
|
inline bool in_range(Value &&value, From &&from, Till &&till) {
|
|
return (value >= from) && (value < till);
|
|
}
|
|
|
|
} // namespace base
|
|
|
|
// using for_const instead of plain range-based for loop to ensure usage of const_iterator
|
|
// it is important for the copy-on-write Qt containers
|
|
// if you have "QVector<T*> v" then "for (T * const p : v)" will still call QVector::detach(),
|
|
// while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile
|
|
#define for_const(range_declaration, range_expression) for (range_declaration : std::as_const(range_expression))
|
|
|
|
template <typename Lambda>
|
|
inline void InvokeQueued(QObject *context, Lambda &&lambda) {
|
|
QObject proxy;
|
|
QObject::connect(&proxy, &QObject::destroyed, context, std::forward<Lambda>(lambda), Qt::QueuedConnection);
|
|
}
|
|
|
|
static const int32 ScrollMax = INT_MAX;
|
|
|
|
extern uint64 _SharedMemoryLocation[];
|
|
template <typename T, unsigned int N>
|
|
T *SharedMemoryLocation() {
|
|
static_assert(N < 4, "Only 4 shared memory locations!");
|
|
return reinterpret_cast<T*>(_SharedMemoryLocation + N);
|
|
}
|
|
|
|
// see https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf
|
|
class str_const { // constexpr string
|
|
public:
|
|
template<std::size_t N>
|
|
constexpr str_const(const char(&a)[N]) : _str(a), _size(N - 1) {
|
|
}
|
|
constexpr char operator[](std::size_t n) const {
|
|
return (n < _size) ? _str[n] :
|
|
#ifndef OS_MAC_OLD
|
|
throw std::out_of_range("");
|
|
#else // OS_MAC_OLD
|
|
throw std::exception();
|
|
#endif // OS_MAC_OLD
|
|
}
|
|
constexpr std::size_t size() const { return _size; }
|
|
const char *c_str() const { return _str; }
|
|
|
|
private:
|
|
const char* const _str;
|
|
const std::size_t _size;
|
|
|
|
};
|
|
|
|
inline QString str_const_toString(const str_const &str) {
|
|
return QString::fromUtf8(str.c_str(), str.size());
|
|
}
|
|
|
|
inline QByteArray str_const_toByteArray(const str_const &str) {
|
|
return QByteArray::fromRawData(str.c_str(), str.size());
|
|
}
|
|
|
|
template <typename T>
|
|
inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; }
|
|
|
|
template <typename T>
|
|
inline void accumulate_min(T &a, const T &b) { if (a > b) a = b; }
|
|
|
|
class Exception : public std::exception {
|
|
public:
|
|
Exception(const QString &msg, bool isFatal = true) : _fatal(isFatal), _msg(msg.toUtf8()) {
|
|
LOG(("Exception: %1").arg(msg));
|
|
}
|
|
bool fatal() const {
|
|
return _fatal;
|
|
}
|
|
|
|
virtual const char *what() const throw() {
|
|
return _msg.constData();
|
|
}
|
|
virtual ~Exception() throw() {
|
|
}
|
|
|
|
private:
|
|
bool _fatal;
|
|
QByteArray _msg;
|
|
|
|
};
|
|
|
|
using TimeId = int32;
|
|
void unixtimeInit();
|
|
void unixtimeSet(TimeId serverTime, bool force = false);
|
|
TimeId unixtime();
|
|
uint64 msgid();
|
|
int32 reqid();
|
|
|
|
QDateTime ParseDateTime(TimeId serverTime);
|
|
|
|
inline void mylocaltime(struct tm * _Tm, const time_t * _Time) {
|
|
#ifdef Q_OS_WIN
|
|
localtime_s(_Tm, _Time);
|
|
#else
|
|
localtime_r(_Time, _Tm);
|
|
#endif
|
|
}
|
|
|
|
namespace ThirdParty {
|
|
|
|
void start();
|
|
void finish();
|
|
|
|
}
|
|
|
|
using TimeMs = int64;
|
|
bool checkms(); // returns true if time has changed
|
|
TimeMs getms(bool checked = false);
|
|
|
|
const static uint32 _md5_block_size = 64;
|
|
class HashMd5 {
|
|
public:
|
|
|
|
HashMd5(const void *input = 0, uint32 length = 0);
|
|
void feed(const void *input, uint32 length);
|
|
int32 *result();
|
|
|
|
private:
|
|
|
|
void init();
|
|
void finalize();
|
|
void transform(const uchar *block);
|
|
|
|
bool _finalized;
|
|
uchar _buffer[_md5_block_size];
|
|
uint32 _count[2];
|
|
uint32 _state[4];
|
|
uchar _digest[16];
|
|
|
|
};
|
|
|
|
int32 hashCrc32(const void *data, uint32 len);
|
|
|
|
int32 *hashSha1(const void *data, uint32 len, void *dest); // dest - ptr to 20 bytes, returns (int32*)dest
|
|
inline std::array<char, 20> hashSha1(const void *data, int size) {
|
|
auto result = std::array<char, 20>();
|
|
hashSha1(data, size, result.data());
|
|
return result;
|
|
}
|
|
|
|
int32 *hashSha256(const void *data, uint32 len, void *dest); // dest - ptr to 32 bytes, returns (int32*)dest
|
|
inline std::array<char, 32> hashSha256(const void *data, int size) {
|
|
auto result = std::array<char, 32>();
|
|
hashSha256(data, size, result.data());
|
|
return result;
|
|
}
|
|
|
|
int32 *hashMd5(const void *data, uint32 len, void *dest); // dest = ptr to 16 bytes, returns (int32*)dest
|
|
inline std::array<char, 16> hashMd5(const void *data, int size) {
|
|
auto result = std::array<char, 16>();
|
|
hashMd5(data, size, result.data());
|
|
return result;
|
|
}
|
|
|
|
char *hashMd5Hex(const int32 *hashmd5, void *dest); // dest = ptr to 32 bytes, returns (char*)dest
|
|
inline char *hashMd5Hex(const void *data, uint32 len, void *dest) { // dest = ptr to 32 bytes, returns (char*)dest
|
|
return hashMd5Hex(HashMd5(data, len).result(), dest);
|
|
}
|
|
inline std::array<char, 32> hashMd5Hex(const void *data, int size) {
|
|
auto result = std::array<char, 32>();
|
|
hashMd5Hex(data, size, result.data());
|
|
return result;
|
|
}
|
|
|
|
// good random (using openssl implementation)
|
|
void memset_rand(void *data, uint32 len);
|
|
template <typename T>
|
|
T rand_value() {
|
|
T result;
|
|
memset_rand(&result, sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
inline void memset_rand_bad(void *data, uint32 len) {
|
|
for (uchar *i = reinterpret_cast<uchar*>(data), *e = i + len; i != e; ++i) {
|
|
*i = uchar(rand() & 0xFF);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void memsetrnd_bad(T &value) {
|
|
memset_rand_bad(&value, sizeof(value));
|
|
}
|
|
|
|
class ReadLockerAttempt {
|
|
public:
|
|
ReadLockerAttempt(not_null<QReadWriteLock*> lock) : _lock(lock), _locked(_lock->tryLockForRead()) {
|
|
}
|
|
ReadLockerAttempt(const ReadLockerAttempt &other) = delete;
|
|
ReadLockerAttempt &operator=(const ReadLockerAttempt &other) = delete;
|
|
ReadLockerAttempt(ReadLockerAttempt &&other) : _lock(other._lock), _locked(base::take(other._locked)) {
|
|
}
|
|
ReadLockerAttempt &operator=(ReadLockerAttempt &&other) {
|
|
_lock = other._lock;
|
|
_locked = base::take(other._locked);
|
|
return *this;
|
|
}
|
|
~ReadLockerAttempt() {
|
|
if (_locked) {
|
|
_lock->unlock();
|
|
}
|
|
}
|
|
|
|
operator bool() const {
|
|
return _locked;
|
|
}
|
|
|
|
private:
|
|
not_null<QReadWriteLock*> _lock;
|
|
bool _locked = false;
|
|
|
|
};
|
|
|
|
inline QString fromUtf8Safe(const char *str, int32 size = -1) {
|
|
if (!str || !size) return QString();
|
|
if (size < 0) size = int32(strlen(str));
|
|
QString result(QString::fromUtf8(str, size));
|
|
QByteArray back = result.toUtf8();
|
|
if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size);
|
|
return result;
|
|
}
|
|
|
|
inline QString fromUtf8Safe(const QByteArray &str) {
|
|
return fromUtf8Safe(str.constData(), str.size());
|
|
}
|
|
|
|
static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption);
|
|
|
|
template <typename T>
|
|
inline T snap(const T &v, const T &_min, const T &_max) {
|
|
return (v < _min) ? _min : ((v > _max) ? _max : v);
|
|
}
|
|
|
|
template <typename T>
|
|
class ManagedPtr {
|
|
public:
|
|
ManagedPtr() = default;
|
|
ManagedPtr(T *p) : _data(p) {
|
|
}
|
|
T *operator->() const {
|
|
return _data;
|
|
}
|
|
T *v() const {
|
|
return _data;
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return _data != nullptr;
|
|
}
|
|
|
|
protected:
|
|
using Parent = ManagedPtr<T>;
|
|
T *_data = nullptr;
|
|
|
|
};
|
|
|
|
QString translitRusEng(const QString &rus);
|
|
QString rusKeyboardLayoutSwitch(const QString &from);
|
|
|
|
enum DBISendKey {
|
|
dbiskEnter = 0,
|
|
dbiskCtrlEnter = 1,
|
|
};
|
|
|
|
enum DBINotifyView {
|
|
dbinvShowPreview = 0,
|
|
dbinvShowName = 1,
|
|
dbinvShowNothing = 2,
|
|
};
|
|
|
|
enum DBIWorkMode {
|
|
dbiwmWindowAndTray = 0,
|
|
dbiwmTrayOnly = 1,
|
|
dbiwmWindowOnly = 2,
|
|
};
|
|
|
|
struct ProxyData {
|
|
enum class Type {
|
|
None,
|
|
Socks5,
|
|
Http,
|
|
Mtproto,
|
|
};
|
|
|
|
Type type = Type::None;
|
|
QString host;
|
|
uint32 port = 0;
|
|
QString user, password;
|
|
|
|
std::vector<QString> resolvedIPs;
|
|
TimeMs resolvedExpireAt = 0;
|
|
|
|
bool valid() const;
|
|
bool supportsCalls() const;
|
|
bool tryCustomResolve() const;
|
|
explicit operator bool() const;
|
|
bool operator==(const ProxyData &other) const;
|
|
bool operator!=(const ProxyData &other) const;
|
|
|
|
static bool ValidSecret(const QString &secret);
|
|
|
|
};
|
|
|
|
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex = 0);
|
|
QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
|
|
|
|
enum DBIScale {
|
|
dbisAuto = 0,
|
|
dbisOne = 1,
|
|
dbisOneAndQuarter = 2,
|
|
dbisOneAndHalf = 3,
|
|
dbisTwo = 4,
|
|
|
|
dbisScaleCount = 5,
|
|
};
|
|
|
|
static const int MatrixRowShift = 40000;
|
|
|
|
enum DBIPlatform {
|
|
dbipWindows = 0,
|
|
dbipMac = 1,
|
|
dbipLinux64 = 2,
|
|
dbipLinux32 = 3,
|
|
dbipMacOld = 4,
|
|
};
|
|
|
|
enum DBIPeerReportSpamStatus {
|
|
dbiprsNoButton = 0, // hidden, but not in the cloud settings yet
|
|
dbiprsUnknown = 1, // contacts not loaded yet
|
|
dbiprsShowButton = 2, // show report spam button, each show peer request setting from cloud
|
|
dbiprsReportSent = 3, // report sent, but the report spam panel is not hidden yet
|
|
dbiprsHidden = 4, // hidden in the cloud or not needed (bots, contacts, etc), no more requests
|
|
dbiprsRequesting = 5, // requesting the cloud setting right now
|
|
};
|
|
|
|
class MimeType {
|
|
public:
|
|
enum class Known {
|
|
Unknown,
|
|
TDesktopTheme,
|
|
TDesktopPalette,
|
|
WebP,
|
|
};
|
|
|
|
MimeType(const QMimeType &type) : _typeStruct(type) {
|
|
}
|
|
MimeType(Known type) : _type(type) {
|
|
}
|
|
QStringList globPatterns() const;
|
|
QString filterString() const;
|
|
QString name() const;
|
|
|
|
private:
|
|
QMimeType _typeStruct;
|
|
Known _type = Known::Unknown;
|
|
|
|
};
|
|
|
|
MimeType mimeTypeForName(const QString &mime);
|
|
MimeType mimeTypeForFile(const QFileInfo &file);
|
|
MimeType mimeTypeForData(const QByteArray &data);
|
|
|
|
#include <cmath>
|
|
|
|
inline int rowscount(int fullCount, int countPerRow) {
|
|
return (fullCount + countPerRow - 1) / countPerRow;
|
|
}
|
|
inline int floorclamp(int value, int step, int lowest, int highest) {
|
|
return qMin(qMax(value / step, lowest), highest);
|
|
}
|
|
inline int floorclamp(float64 value, int step, int lowest, int highest) {
|
|
return qMin(qMax(static_cast<int>(std::floor(value / step)), lowest), highest);
|
|
}
|
|
inline int ceilclamp(int value, int step, int lowest, int highest) {
|
|
return qMax(qMin((value + step - 1) / step, highest), lowest);
|
|
}
|
|
inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) {
|
|
return qMax(qMin(static_cast<int>(std::ceil(value / step)), highest), lowest);
|
|
}
|
|
|
|
static int32 FullArcLength = 360 * 16;
|
|
static int32 QuarterArcLength = (FullArcLength / 4);
|
|
static int32 MinArcLength = (FullArcLength / 360);
|
|
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
|
|
|
|
// This pointer is used for global non-POD variables that are allocated
|
|
// on demand by createIfNull(lambda) and are never automatically freed.
|
|
template <typename T>
|
|
class NeverFreedPointer {
|
|
public:
|
|
NeverFreedPointer() = default;
|
|
NeverFreedPointer(const NeverFreedPointer<T> &other) = delete;
|
|
NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete;
|
|
|
|
template <typename... Args>
|
|
void createIfNull(Args&&... args) {
|
|
if (isNull()) {
|
|
reset(new T(std::forward<Args>(args)...));
|
|
}
|
|
};
|
|
|
|
T *data() const {
|
|
return _p;
|
|
}
|
|
T *release() {
|
|
return base::take(_p);
|
|
}
|
|
void reset(T *p = nullptr) {
|
|
delete _p;
|
|
_p = p;
|
|
}
|
|
bool isNull() const {
|
|
return data() == nullptr;
|
|
}
|
|
|
|
void clear() {
|
|
reset();
|
|
}
|
|
T *operator->() const {
|
|
return data();
|
|
}
|
|
T &operator*() const {
|
|
Assert(!isNull());
|
|
return *data();
|
|
}
|
|
explicit operator bool() const {
|
|
return !isNull();
|
|
}
|
|
|
|
private:
|
|
T *_p;
|
|
|
|
};
|
|
|
|
// This pointer is used for static non-POD variables that are allocated
|
|
// on first use by constructor and are never automatically freed.
|
|
template <typename T>
|
|
class StaticNeverFreedPointer {
|
|
public:
|
|
explicit StaticNeverFreedPointer(T *p) : _p(p) {
|
|
}
|
|
StaticNeverFreedPointer(const StaticNeverFreedPointer<T> &other) = delete;
|
|
StaticNeverFreedPointer &operator=(const StaticNeverFreedPointer<T> &other) = delete;
|
|
|
|
T *data() const {
|
|
return _p;
|
|
}
|
|
T *release() {
|
|
return base::take(_p);
|
|
}
|
|
void reset(T *p = nullptr) {
|
|
delete _p;
|
|
_p = p;
|
|
}
|
|
bool isNull() const {
|
|
return data() == nullptr;
|
|
}
|
|
|
|
void clear() {
|
|
reset();
|
|
}
|
|
T *operator->() const {
|
|
return data();
|
|
}
|
|
T &operator*() const {
|
|
Assert(!isNull());
|
|
return *data();
|
|
}
|
|
explicit operator bool() const {
|
|
return !isNull();
|
|
}
|
|
|
|
private:
|
|
T *_p = nullptr;
|
|
|
|
};
|