version 0.6.12 - local image cache, drafts, shared contact fix, some network fixes

This commit is contained in:
John Preston 2014-11-22 12:45:04 +03:00
parent 5d649f750b
commit f370e2b85d
51 changed files with 1760 additions and 375 deletions

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinuxupd$AppVersion" ]; then
echo "tlinuxupd$AppVersion not found!";

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinux32upd$AppVersion" ]; then
echo "tlinux32upd$AppVersion not found!"

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ ! -f "./../Mac/Release/deploy/$AppVersionStr/tmacupd$AppVersion" ]; then
echo "tmacupd$AppVersion not found!"

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ ! -f "./../Win32/Deploy/deploy/$AppVersionStr/tupdate$AppVersion" ]; then
echo "tupdate$AppVersion not found!"

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@ -1,5 +1,5 @@
AppVersionStr=0.6.11
AppVersion=6011
AppVersionStr=0.6.12
AppVersion=6012
echo ""
echo "Preparing version $AppVersionStr.."

View File

@ -1,6 +1,6 @@
@echo OFF
set "AppVersionStr=0.6.11"
set "AppVersionStr=0.6.12"
echo.
echo Preparing version %AppVersionStr%..
echo.

View File

@ -62,6 +62,7 @@ lng_connecting: "Connecting..";
lng_reconnecting: "Reconnect in %1 s..";
lng_reconnecting_try_now: "Try now";
lng_status_service_notifications: "service notifications";
lng_status_offline: "last seen a long time ago";
lng_status_recently: "last seen recently";
lng_status_last_week: "last seen within a week";
@ -219,6 +220,15 @@ lng_download_path_clearing: "Clearing..";
lng_download_path_cleared: "Cleared!";
lng_download_path_clear_failed: "Clear failed :(";
lng_settings_section_cache: "Local storage";
lng_settings_no_images_cached: "No cached images found!";
lng_settings_image_cached: "Cached: {count} image, {size}";
lng_settings_images_cached: "Cached: {count} images, {size}";
lng_local_images_clear: "Clear All";
lng_local_images_clearing: "Clearing..";
lng_local_images_cleared: "Cleared!";
lng_local_images_clear_failed: "Clear failed :(";
lng_settings_section_advanced: "Advanced";
lng_connection_type: "Connection type:";
lng_connection_auto_connecting: "Default (connecting..)";

View File

@ -3,9 +3,9 @@
#define MyAppShortName "Telegram"
#define MyAppName "Telegram Desktop"
#define MyAppVersion "0.6.11"
#define MyAppVersionZero "0.6.11"
#define MyAppFullVersion "0.6.11.0"
#define MyAppVersion "0.6.12"
#define MyAppVersionZero "0.6.12"
#define MyAppFullVersion "0.6.12.0"
#define MyAppPublisher "Telegram Messenger LLP"
#define MyAppURL "https://tdesktop.com"
#define MyAppExeName "Telegram.exe"

View File

@ -26,6 +26,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mainwidget.h"
#include <libexif/exif-data.h>
#include "localstorage.h"
namespace {
bool quiting = false;
@ -114,7 +116,7 @@ namespace App {
bool loggedOut() {
Window *w(wnd());
if (w) {
w->tempDirDelete();
w->tempDirDelete(Local::ClearManagerAll);
w->notifyClearFast();
w->setupIntro(true);
}
@ -170,7 +172,7 @@ namespace App {
switch (online) {
case -2: {
QDate yesterday(date(now).date());
yesterday.addDays(-1);
yesterday.addDays(-3);
return int32(QDateTime(yesterday).toTime_t());
} break;
@ -207,7 +209,11 @@ namespace App {
return dNow.secsTo(dTomorrow);
}
QString onlineText(int32 online, int32 now, bool precise) {
QString onlineText(UserData *user, int32 now, bool precise) {
if (isServiceUser(user->id)) {
return lang(lng_status_service_notifications);
}
int32 online = user->onlineTill;
if (online <= 0) {
switch (online) {
case 0: return lang(lng_status_offline);
@ -337,7 +343,7 @@ namespace App {
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
data->setPhone(qs(d.vphone));
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), (data->id != 333000 && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), (!isServiceUser(data->id) && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
data->setPhoto(d.vphoto);
data->access = d.vaccess_hash.v;
wasContact = (data->contact > 0);
@ -683,7 +689,7 @@ namespace App {
App::main()->removeContact(user);
}
}
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || user->id == 333000 || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || isServiceUser(user->id) || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
if (App::main()) App::main()->peerUpdated(user);
}
}
@ -1103,7 +1109,6 @@ namespace App {
result = new ImageLinkData(imageLink);
imageLinksData.insert(imageLink, result);
result->type = type;
result->openl = TextLinkPtr(new TextLink(url));
} else {
result = i.value();
}
@ -1655,7 +1660,7 @@ namespace App {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("App Info: writing user config file"));
QDataStream configStream(&configFile);
@ -1706,7 +1711,7 @@ namespace App {
}
cSetLocalSalt(salt);
MTP::createLocalKey(QByteArray(), &salt);
Local::createOldKey(&salt);
if (data.size() <= 16 || (data.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(data.size()));
@ -1715,7 +1720,7 @@ namespace App {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("App Error: bad decrypt key, data from user-config not decrypted"));
@ -1942,7 +1947,6 @@ namespace App {
::quiting = true;
}
QImage readImage(QByteArray data, QByteArray *format) {
QByteArray tmpFormat;
QImage result;

View File

@ -65,7 +65,7 @@ namespace App {
int32 onlineForSort(int32 online, int32 now);
int32 onlineWillChangeIn(int32 onlineOnServer, int32 nowOnServer);
QString onlineText(int32 onlineOnServer, int32 nowOnServer, bool precise = false);
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
void feedUsers(const MTPVector<MTPUser> &users);
void feedChats(const MTPVector<MTPChat> &chats);

View File

@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/confirmbox.h"
#include "langloaderplain.h"
#include "localstorage.h"
namespace {
Application *mainApp = 0;
FileUploader *uploader = 0;
@ -131,6 +133,7 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
}
}
Local::start();
style::startManager();
anim::startManager();
historyInit();
@ -617,9 +620,11 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
}
void Application::startApp() {
Local::ReadMapState state = Local::readMap(QByteArray());
App::readUserConfig();
if (!MTP::localKey().created()) {
MTP::createLocalKey(QByteArray());
if (!Local::oldKey().created()) {
Local::createOldKey();
cSetNeedConfigResave(true);
}
if (cNeedConfigResave()) {
@ -765,6 +770,7 @@ Application::~Application() {
delete window;
style::stopManager();
Local::stop();
}
Application *Application::app() {

View File

@ -80,6 +80,8 @@ signals:
void peerPhotoDone(PeerId peer);
void peerPhotoFail(PeerId peer);
void adjustSingleTimers();
public slots:
void startUpdateCheck(bool forceWait = false);

View File

@ -118,7 +118,7 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro
data->inchat = _chat->participants.constFind(user) != _chat->participants.cend();
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@ -98,7 +98,7 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
if (i == _contactsData.cend()) {
_contactsData.insert(user, data = new ContactData());
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@ -99,7 +99,7 @@ NewGroupInner::ContactData *NewGroupInner::contactData(DialogRow *row) {
_contactsData.insert(user, data = new ContactData());
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
static const int32 AppVersion = 6011;
static const wchar_t *AppVersionStr = L"0.6.11";
static const int32 AppVersion = 6012;
static const wchar_t *AppVersionStr = L"0.6.12";
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
static const wchar_t *AppName = L"Telegram Desktop";
@ -101,8 +101,16 @@ enum {
MaxMessageSize = 4096,
MaxHttpRedirects = 5, // when getting external data/images
WriteMapTimeout = 1000,
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
};
inline bool isServiceUser(uint64 id) {
return (id == 333000) || (id == 777000);
}
#ifdef Q_OS_WIN
inline const GUID &cGUID() {
static const GUID gGuid = { 0x87a94ab0, 0xe370, 0x4cde, { 0x98, 0xd3, 0xac, 0xc1, 0x10, 0xc5, 0x96, 0x7d } };

View File

@ -29,18 +29,9 @@ namespace {
return img;
}
typedef QMap<QByteArray, StorageImage*> StorageImages;
typedef QMap<StorageKey, StorageImage*> StorageImages;
StorageImages storageImages;
QByteArray storageKey(int32 dc, const int64 &volume, int32 local, const int64 &secret) {
QByteArray result(24, Qt::Uninitialized);
memcpy(result.data(), &dc, 4);
memcpy(result.data() + 4, &volume, 8);
memcpy(result.data() + 12, &local, 4);
memcpy(result.data() + 16, &secret, 8);
return result;
}
int64 globalAquiredSize = 0;
}
@ -480,7 +471,7 @@ bool StorageImage::loaded() const {
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, size));
@ -489,7 +480,7 @@ StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume,
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
QByteArray bytesArr(bytes);

View File

@ -53,6 +53,13 @@ public:
void forget() const;
void restore() const;
QByteArray savedFormat() const {
return format;
}
QByteArray savedData() const {
return saved;
}
virtual ~Image() {
invalidateSizeCache();
}
@ -106,6 +113,24 @@ private:
LocalImage *getImage(const QString &file);
LocalImage *getImage(const QPixmap &pixmap, QByteArray format);
typedef QPair<uint64, uint64> StorageKey;
inline uint64 storageMix32To64(int32 a, int32 b) {
return (uint64(*reinterpret_cast<uint32*>(&a)) << 32) | uint64(*reinterpret_cast<uint32*>(&b));
}
inline StorageKey storageKey(int32 dc, const int64 &volume, int32 local) {
return StorageKey(storageMix32To64(dc, local), volume);
}
inline StorageKey storageKey(const MTPDfileLocation &location) {
return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v);
}
struct StorageImageSaved {
StorageImageSaved() : type(mtpc_storage_fileUnknown) {
}
StorageImageSaved(mtpTypeId type, const QByteArray &data) : type(type), data(data) {
}
mtpTypeId type;
QByteArray data;
};
class StorageImage : public Image {
public:
@ -123,7 +148,7 @@ public:
void load(bool loadFirst = false, bool prior = true) {
if (loader) {
loader->start(loadFirst, prior);
check();
if (loader) check();
}
}
void checkload() const {
@ -131,7 +156,7 @@ public:
if (!loader->loading()) {
loader->start(true);
}
check();
if (loader) check();
}
}

View File

@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "gui/filedialog.h"
#include "audio.h"
#include "localstorage.h"
TextParseOptions _textNameOptions = {
0, // flags
@ -2132,6 +2133,10 @@ const QString HistoryPhoto::inDialogsText() const {
return lang(lng_in_dlg_photo);
}
const QString HistoryPhoto::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_photo) + qsl(" ]");
}
bool HistoryPhoto::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y >= 0 && x < width && y < _height);
@ -2149,6 +2154,40 @@ HistoryMedia *HistoryPhoto::clone() const {
return new HistoryPhoto(*this);
}
void HistoryPhoto::updateFrom(const MTPMessageMedia &media) {
if (media.type() == mtpc_messageMediaPhoto) {
const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto);
if (photo.type() == mtpc_photo) {
const QVector<MTPPhotoSize> &sizes(photo.c_photo().vsizes.c_vector().v);
for (QVector<MTPPhotoSize>::const_iterator i = sizes.cbegin(), e = sizes.cend(); i != e; ++i) {
char size = 0;
const MTPFileLocation *loc = 0;
switch (i->type()) {
case mtpc_photoSize: {
const string &s(i->c_photoSize().vtype.c_string().v);
loc = &i->c_photoSize().vlocation;
if (s.size()) size = s[0];
} break;
case mtpc_photoCachedSize: {
const string &s(i->c_photoCachedSize().vtype.c_string().v);
loc = &i->c_photoSize().vlocation;
if (s.size()) size = s[0];
} break;
}
if (!loc || loc->type() != mtpc_fileLocation) continue;
if (size == 's') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->thumb);
} else if (size == 'm') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->medium);
} else if (size == 'x') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->full);
}
}
}
}
}
void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
if (width < 0) width = w;
data->full->load(false, false);
@ -2314,6 +2353,10 @@ const QString HistoryVideo::inDialogsText() const {
return lang(lng_in_dlg_video);
}
const QString HistoryVideo::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_video) + qsl(" ]");
}
bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@ -2611,6 +2654,10 @@ const QString HistoryAudio::inDialogsText() const {
return lang(lng_in_dlg_audio);
}
const QString HistoryAudio::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]");
}
bool HistoryAudio::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@ -2862,6 +2909,10 @@ const QString HistoryDocument::inDialogsText() const {
return data->name.isEmpty() ? lang(lng_in_dlg_document) : data->name;
}
const QString HistoryDocument::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_document) + (data->name.isEmpty() ? QString() : (qsl(" : ") + data->name)) + qsl(" ]");
}
bool HistoryDocument::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@ -2962,6 +3013,10 @@ const QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact);
}
const QString HistoryContact::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + name.original(0, 0xFFFF, false) + qsl(", ") + phone + qsl(" ]");
}
bool HistoryContact::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y <= 0 && x < w && y < _height);
@ -3060,7 +3115,7 @@ void HistoryContact::updateFrom(const MTPMessageMedia &media) {
}
namespace {
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.)?youtube\\.com/watch\\?v=([a-z0-9_-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.|m\\.)?youtube\\.com/watch\\?([^#]+&)?v=([a-z0-9_-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)(/|$)"), QRegularExpression::CaseInsensitiveOption);
@ -3373,15 +3428,23 @@ void ImageLinkData::load() {
HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : HistoryMedia(width) {
if (url.startsWith(qsl("location:"))) {
data = App::imageLink(url, GoogleMapsLink, qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"));
QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17");
link.reset(new TextLink(lnk));
data = App::imageLink(url, GoogleMapsLink, lnk);
} else {
int matchIndex = 4;
QRegularExpressionMatch m = reYouTube1.match(url);
if (!m.hasMatch()) m = reYouTube2.match(url);
if (!m.hasMatch()) {
m = reYouTube2.match(url);
matchIndex = 3;
}
if (m.hasMatch()) {
data = App::imageLink(qsl("youtube:") + m.captured(3), YouTubeLink, url);
link.reset(new TextLink(url));
data = App::imageLink(qsl("youtube:") + m.captured(matchIndex), YouTubeLink, url);
} else {
m = reInstagram.match(url);
if (m.hasMatch()) {
link.reset(new TextLink(url));
data = App::imageLink(qsl("instagram:") + m.captured(3), InstagramLink, url);
data->title = qsl("instagram.com/p/") + m.captured(3);
} else {
@ -3552,6 +3615,17 @@ const QString HistoryImageLink::inDialogsText() const {
return QString();
}
const QString HistoryImageLink::inHistoryText() const {
if (data) {
switch (data->type) {
case YouTubeLink: return qsl("[ YouTube Video : ") + link->text() + qsl(" ]");
case InstagramLink: return qsl("[ Instagram Link : ") + link->text() + qsl(" ]");
case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + link->text() + qsl(" ]");
}
}
return qsl("[ Link : ") + link->text() + qsl(" ]");
}
bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y >= 0 && x < width && y < _height);
@ -3560,7 +3634,7 @@ bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int
TextLinkPtr HistoryImageLink::getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (x >= 0 && y >= 0 && x < width && y < _height && data) {
return data->openl;
return link;
}
return TextLinkPtr();
}
@ -3705,7 +3779,7 @@ bool HistoryMessage::uploading() const {
QString HistoryMessage::selectedText(uint32 selection) const {
if (_media && selection == FullItemSel) {
return _text.original(0, 0xFFFF) + '[' + _media->inDialogsText() + ']';
return _text.original(0, 0xFFFF) + _media->inHistoryText();
}
uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF;
uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF);

View File

@ -629,6 +629,34 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) {
return MTPMessagesFilter();
}
struct MessageCursor {
MessageCursor() : position(0), anchor(0), scroll(QFIXED_MAX) {
}
MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) {
}
MessageCursor(const QTextEdit &edit) {
fillFrom(edit);
}
void fillFrom(const QTextEdit &edit) {
QTextCursor c = edit.textCursor();
position = c.position();
anchor = c.anchor();
QScrollBar *s = edit.verticalScrollBar();
scroll = s ? s->value() : QFIXED_MAX;
}
void applyTo(QTextEdit &edit, bool *lock = 0) {
if (lock) *lock = true;
QTextCursor c = edit.textCursor();
c.setPosition(anchor, QTextCursor::MoveAnchor);
c.setPosition(position, QTextCursor::KeepAnchor);
edit.setTextCursor(c);
QScrollBar *s = edit.verticalScrollBar();
if (s) s->setValue(scroll);
if (lock) *lock = false;
}
int position, anchor, scroll;
};
class HistoryMedia;
class HistoryMessage;
class HistoryUnreadBar;
@ -732,7 +760,7 @@ struct History : public QList<HistoryBlock*> {
}
QString draft;
QTextCursor draftCur;
MessageCursor draftCursor;
int32 lastWidth, lastScrollTop;
bool mute;
@ -1223,6 +1251,7 @@ public:
virtual HistoryMediaType type() const = 0;
virtual const QString inDialogsText() const = 0;
virtual const QString inHistoryText() const = 0;
virtual bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0;
virtual int32 countHeight(const HistoryItem *parent, int32 width = -1) const {
return height();
@ -1276,6 +1305,7 @@ public:
return MediaTypePhoto;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
@ -1284,6 +1314,8 @@ public:
return data;
}
void updateFrom(const MTPMessageMedia &media);
TextLinkPtr lnk() const {
return openl;
}
@ -1312,6 +1344,7 @@ public:
return MediaTypeVideo;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@ -1344,6 +1377,7 @@ public:
return MediaTypeAudio;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@ -1376,6 +1410,7 @@ public:
return MediaTypeDocument;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
int32 countHeight(const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@ -1418,6 +1453,7 @@ public:
return MediaTypeContact;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
HistoryMedia *clone() const;
@ -1426,7 +1462,7 @@ public:
private:
int32 userId;
int32 w, phonew;
int32 phonew;
Text name;
QString phone;
UserData *contact;
@ -1449,7 +1485,6 @@ struct ImageLinkData {
QString id;
QString title, duration;
ImagePtr thumb;
TextLinkPtr openl;
ImageLinkType type;
bool loading;
@ -1498,12 +1533,14 @@ public:
return MediaTypeImageLink;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
private:
ImageLinkData *data;
TextLinkPtr link;
};

View File

@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "fileuploader.h"
#include "supporttl.h"
#include "localstorage.h"
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
HistoryList::HistoryList(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0)
@ -633,106 +635,98 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
_contextMenuLnk = textlnkOver();
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (_menu) {
_menu->deleteOnHide();
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
@ -1535,7 +1529,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, _attachDragDocument(this)
, _attachDragPhoto(this)
, imageLoader(this)
, noTypingUpdate(false)
, _synthedTextUpdate(false)
, loadingChatId(0)
, loadingRequestId(0)
, serviceImageCacheSize(0)
@ -1545,7 +1539,9 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, bg(st::msgBG)
, hiderOffered(false)
, _scrollDelta(0)
, _typingRequest(0) {
, _typingRequest(0)
, _saveDraftText(false)
, _saveDraftStart(0) {
_scroll.setFocusPolicy(Qt::NoFocus);
setAcceptDrops(true);
@ -1575,6 +1571,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
_animActiveTimer.setSingleShot(false);
connect(&_animActiveTimer, SIGNAL(timeout()), this, SLOT(onAnimActiveStep()));
_saveDraftTimer.setSingleShot(true);
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
_scroll.hide();
_scroll.move(0, 0);
@ -1605,6 +1606,43 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
void HistoryWidget::onTextChange() {
updateTyping();
if (!hist || _synthedTextUpdate) return;
_saveDraftText = true;
onDraftSave(true);
}
void HistoryWidget::onDraftSaveDelayed() {
if (!hist || _synthedTextUpdate) return;
if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) {
if (!Local::hasDraftPositions(hist->peer->id)) return;
}
onDraftSave(true);
}
void HistoryWidget::onDraftSave(bool delayed) {
if (!hist) return;
if (delayed) {
uint64 ms = getms();
if (!_saveDraftStart) {
_saveDraftStart = ms;
return _saveDraftTimer.start(SaveDraftTimeout);
} else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) {
return _saveDraftTimer.start(SaveDraftTimeout);
}
}
writeDraft();
}
void HistoryWidget::writeDraft(const QString *text, const MessageCursor *cursor) {
bool save = hist && (_saveDraftStart > 0);
_saveDraftStart = 0;
_saveDraftTimer.stop();
if (_saveDraftText) {
if (save) Local::writeDraft(hist->peer->id, text ? (*text) : _field.getText());
_saveDraftText = false;
}
if (save) Local::writeDraftPositions(hist->peer->id, cursor ? (*cursor) : MessageCursor(_field));
}
void HistoryWidget::cancelTyping() {
@ -1616,7 +1654,7 @@ void HistoryWidget::cancelTyping() {
void HistoryWidget::updateTyping(bool typing) {
uint64 ms = getms(true) + 10000;
if (noTypingUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
if (_synthedTextUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
hist->myTyping = typing ? ms : 0;
cancelTyping();
@ -1725,7 +1763,10 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
}
if (hist) {
hist->draft = _field.getText();
hist->draftCur = _field.textCursor();
hist->draftCursor.fillFrom(_field);
writeDraft(&hist->draft, &hist->draftCursor);
if (hist->readyForWork() && _scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) {
hist->lastWidth = _list->width();
} else {
@ -1803,13 +1844,19 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
App::main()->peerUpdated(histPeer);
noTypingUpdate = true;
setFieldText(hist->draft);
_field.setFocus();
if (!hist->draft.isEmpty()) {
_field.setTextCursor(hist->draftCur);
setFieldText(hist->draft);
_field.setFocus();
hist->draftCursor.applyTo(_field, &_synthedTextUpdate);
} else {
QString draft = Local::readDraft(hist->peer->id);
setFieldText(draft);
_field.setFocus();
if (!draft.isEmpty()) {
MessageCursor cur = Local::readDraftPositions(hist->peer->id);
cur.applyTo(_field, &_synthedTextUpdate);
}
}
noTypingUpdate = false;
connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected()));
@ -2223,6 +2270,10 @@ void HistoryWidget::onSend(bool ctrlShiftEnter) {
App::main()->sendPreparedText(hist, prepareMessage(_field.getText()));
setFieldText(QString());
_saveDraftText = true;
_saveDraftStart = getms();
onDraftSave();
if (!_attachType.isHidden()) _attachType.hideStart();
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
}
@ -2688,7 +2739,7 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
}
}
} else {
text = App::onlineText(histPeer->asUser()->onlineTill, t);
text = App::onlineText(histPeer->asUser(), t);
}
if (titlePeerText != text) {
titlePeerText = text;
@ -3164,9 +3215,9 @@ void HistoryWidget::onFieldTabbed() {
}
void HistoryWidget::setFieldText(const QString &text) {
noTypingUpdate = true;
_synthedTextUpdate = true;
_field.setPlainText(text);
noTypingUpdate = false;
_synthedTextUpdate = false;
}
void HistoryWidget::onCancel() {

View File

@ -396,6 +396,9 @@ public slots:
void onAnimActiveStep();
void onDraftSaveDelayed();
void onDraftSave(bool delayed = false);
private:
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
@ -404,6 +407,7 @@ private:
void addMessagesToBack(const QVector<MTPMessage> &messages);
void chatLoaded(const MTPmessages_ChatFull &res);
void writeDraft(const QString *text = 0, const MessageCursor *cursor = 0);
void setFieldText(const QString &text);
QStringList getMediasFromMime(const QMimeData *d);
@ -440,7 +444,7 @@ private:
int32 _selCount; // < 0 - text selected, focus list, not _field
LocalImageLoader imageLoader;
bool noTypingUpdate;
bool _synthedTextUpdate;
PeerId loadingChatId;
mtpRequestId loadingRequestId;
@ -470,5 +474,9 @@ private:
mtpRequestId _typingRequest;
QTimer _typingStopTimer;
uint64 _saveDraftStart;
bool _saveDraftText;
QTimer _saveDraftTimer;
};

View File

@ -0,0 +1,988 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "localstorage.h"
namespace {
typedef quint64 FileKey;
static const char tdfMagic[] = { 'T', 'D', 'F', '$' };
static const int32 tdfMagicLen = sizeof(tdfMagic);
QString toFilePart(FileKey val) {
QString result;
result.reserve(0x10);
for (int32 i = 0; i < 0x10; ++i) {
uchar v = (val & 0x0F);
result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A)));
val >>= 4;
}
return result;
}
FileKey fromFilePart(const QString &val) {
FileKey result = 0;
int32 i = val.size();
if (i != 0x10) return 0;
while (i > 0) {
--i;
result <<= 4;
uint16 ch = val.at(i).unicode();
if (ch >= 'A' && ch <= 'F') {
result |= (ch - 'A') + 0x0A;
} else if (ch >= '0' && ch <= '9') {
result |= (ch - '0');
} else {
return 0;
}
}
return result;
}
QString _basePath;
bool _started = false;
_local_inner::Manager *_manager = 0;
bool _working() {
return _manager && !_basePath.isEmpty();
}
bool keyAlreadyUsed(QString &name) {
name += '0';
if (QFileInfo(name).exists()) return true;
name[name.size() - 1] = '1';
return QFileInfo(name).exists();
}
FileKey genKey() {
if (!_working()) return 0;
FileKey result;
QString path;
path.reserve(_basePath.size() + 0x11);
path += _basePath;
do {
result = MTP::nonce<FileKey>();
path.resize(_basePath.size());
path += toFilePart(result);
} while (keyAlreadyUsed(path));
return result;
}
void clearKey(const FileKey &key, bool safe = true) {
if (!_working()) return;
QString name;
name.reserve(_basePath.size() + 0x11);
name += _basePath;
name += toFilePart(key);
name += '0';
QFile::remove(name);
if (safe) {
name[name.size() - 1] = '1';
QFile::remove(name);
}
}
QByteArray _passKeySalt, _passKeyEncrypted;
mtpAuthKey _oldKey, _passKey, _localKey;
void createLocalKey(const QByteArray &pass, QByteArray *salt, mtpAuthKey *result) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
result->setKey(key);
}
struct FileReadDescriptor {
FileReadDescriptor() : version(0) {
}
int32 version;
QByteArray data;
QBuffer buffer;
QDataStream stream;
~FileReadDescriptor() {
if (version) {
stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
}
};
struct EncryptedDescriptor {
EncryptedDescriptor() {
}
EncryptedDescriptor(uint32 size) {
uint32 fullSize = sizeof(uint32) + size;
if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
data.reserve(fullSize);
data.resize(sizeof(uint32));
buffer.setBuffer(&data);
buffer.open(QIODevice::WriteOnly);
buffer.seek(sizeof(uint32));
stream.setDevice(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
}
QByteArray data;
QBuffer buffer;
QDataStream stream;
void finish() {
if (stream.device()) stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
~EncryptedDescriptor() {
finish();
}
};
struct FileWriteDescriptor {
FileWriteDescriptor(const FileKey &key, bool safe = true) : dataSize(0) {
init(toFilePart(key), safe);
}
FileWriteDescriptor(const QString &name, bool safe = true) : dataSize(0) {
init(name, safe);
}
void init(const QString &name, bool safe) {
if (!_working()) return;
// detect order of read attempts and file version
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry0(toTry[0]);
QFileInfo toTry1(toTry[1]);
if (toTry0.exists()) {
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 > mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
qSwap(toTry[0], toTry[1]);
}
toDelete = toTry[1];
} else if (toTry1.exists()) {
toDelete = toTry[1];
}
}
file.setFileName(toTry[0]);
if (file.open(QIODevice::WriteOnly)) {
file.write(tdfMagic, tdfMagicLen);
qint32 version = AppVersion;
file.write((const char*)&version, sizeof(version));
stream.setDevice(&file);
stream.setVersion(QDataStream::Qt_5_1);
}
}
bool writeData(const QByteArray &data) {
if (!file.isOpen()) return false;
stream << data;
quint32 len = data.isNull() ? 0xffffffff : data.size();
if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
len = qbswap(len);
}
md5.feed(&len, sizeof(len));
md5.feed(data.constData(), data.size());
dataSize += sizeof(len) + data.size();
return true;
}
QByteArray prepareEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
data.finish();
QByteArray &toEncrypt(data.data);
// prepare for encryption
uint32 size = toEncrypt.size(), fullSize = size;
if (fullSize & 0x0F) {
fullSize += 0x10 - (fullSize & 0x0F);
toEncrypt.resize(fullSize);
memset_rand(toEncrypt.data() + size, fullSize - size);
}
*(uint32*)toEncrypt.data() = size;
QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData());
return encrypted;
}
bool writeEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
return writeData(prepareEncrypted(data, key));
}
void finish() {
if (!file.isOpen()) return;
stream.setDevice(0);
md5.feed(&dataSize, sizeof(dataSize));
qint32 version = AppVersion;
md5.feed(&version, sizeof(version));
md5.feed(tdfMagic, tdfMagicLen);
file.write((const char*)md5.result(), 0x10);
file.close();
if (!toDelete.isEmpty()) {
QFile::remove(toDelete);
}
}
QFile file;
QDataStream stream;
QString toDelete;
HashMd5 md5;
int32 dataSize;
~FileWriteDescriptor() {
finish();
}
};
bool readFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!_working()) return false;
// detect order of read attempts
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
QFileInfo toTry0(toTry[0]);
if (toTry0.exists()) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry1(toTry[1]);
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 < mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
toTry[1] = QString();
}
} else {
toTry[0][toTry[0].size() - 1] = '1';
}
}
for (int32 i = 0; i < 2; ++i) {
QString fname(toTry[i]);
if (fname.isEmpty()) break;
QFile f(fname);
if (!f.open(QIODevice::ReadOnly)) {
DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name));
continue;
}
// check magic
char magic[tdfMagicLen];
if (f.read(magic, tdfMagicLen) != tdfMagicLen) {
DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name));
continue;
}
if (memcmp(magic, tdfMagic, tdfMagicLen)) {
DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(mb(magic, tdfMagicLen).str()).arg(name));
continue;
}
// read app version
qint32 version;
if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name));
continue;
}
if (version > AppVersion) {
DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion));
continue;
}
// read data
QByteArray bytes = f.read(f.size());
int32 dataSize = bytes.size() - 16;
if (dataSize < 0) {
DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name));
continue;
}
// check signature
HashMd5 md5;
md5.feed(bytes.constData(), dataSize);
md5.feed(&dataSize, sizeof(dataSize));
md5.feed(&version, sizeof(version));
md5.feed(magic, tdfMagicLen);
if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name));
continue;
}
bytes.resize(dataSize);
result.data = bytes;
bytes = QByteArray();
result.version = version;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
QFile::remove(toTry[1 - i]);
}
return true;
}
return false;
}
bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const mtpAuthKey &key = _localKey) {
if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
return false;
}
uint32 fullLen = encrypted.size() - 16;
QByteArray decrypted;
decrypted.resize(fullLen);
const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
aesDecryptLocal(encryptedData, decrypted.data(), fullLen, &key, encryptedKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
LOG(("App Error: bad decrypt key, data not decrypted"));
return false;
}
uint32 dataLen = *(const uint32*)decrypted.constData();
if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
return false;
}
decrypted.resize(dataLen);
result.data = decrypted;
decrypted = QByteArray();
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(sizeof(uint32)); // skip len
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!readFile(result, name, safe)) {
return false;
}
QByteArray encrypted;
result.stream >> encrypted;
EncryptedDescriptor data;
if (!decryptLocal(data, encrypted)) {
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = QByteArray();
result.version = 0;
return false;
}
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = data.data;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(data.buffer.pos());
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
enum { // Local Storage Keys
lskUserMap = 0,
lskDraft, // data: PeerId peer
lskDraftPosition, // data: PeerId peer
lskStorage, // data: StorageKey location
};
typedef QMap<PeerId, FileKey> DraftsMap;
DraftsMap _draftsMap, _draftsPositionsMap;
typedef QMap<PeerId, bool> DraftsNotReadMap;
DraftsNotReadMap _draftsNotReadMap;
typedef QPair<FileKey, qint32> FileDesc; // file, size
typedef QMap<StorageKey, FileDesc> StorageMap;
StorageMap _storageMap;
int32 _storageFilesSize = 0;
bool _mapChanged = false;
Local::ReadMapState _readMap(const QByteArray &pass) {
uint64 ms = getms();
QByteArray dataNameUtf8 = cDataFile().toUtf8();
uint64 dataNameHash[2];
hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
_basePath = cWorkingDir() + qsl("tdata/") + toFilePart(dataNameHash[0]) + QChar('/');
FileReadDescriptor mapData;
if (!readFile(mapData, qsl("map"))) {
return Local::ReadMapFailed;
}
QByteArray salt, keyEncrypted, mapEncrypted;
mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
if (mapData.stream.status() != QDataStream::Ok) {
LOG(("App Error: could not read salt / key from map file - corrupted?..").arg(mapData.stream.status()));
return Local::ReadMapFailed;
}
if (salt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in map file, size: %1").arg(salt.size()));
return Local::ReadMapFailed;
}
createLocalKey(pass, &salt, &_passKey);
EncryptedDescriptor keyData, map;
if (!decryptLocal(keyData, keyEncrypted, _passKey)) {
LOG(("App Error: could not decrypt pass-protected key from map file, maybe bad password.."));
return Local::ReadMapPassNeeded;
}
uchar key[LocalEncryptKeySize] = { 0 };
if (keyData.stream.readRawData((char*)key, LocalEncryptKeySize) != LocalEncryptKeySize || !keyData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from map file"));
return Local::ReadMapFailed;
}
_localKey.setKey(key);
_passKeyEncrypted = keyEncrypted;
_passKeySalt = salt;
if (!decryptLocal(map, mapEncrypted)) {
LOG(("App Error: could not decrypt map."));
return Local::ReadMapFailed;
}
DraftsMap draftsMap, draftsPositionsMap;
DraftsNotReadMap draftsNotReadMap;
StorageMap storageMap;
qint64 storageFilesSize = 0;
while (!map.stream.atEnd()) {
quint32 keyType;
map.stream >> keyType;
switch (keyType) {
case lskDraft: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsMap.insert(p, key);
draftsNotReadMap.insert(p, true);
}
} break;
case lskDraftPosition: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsPositionsMap.insert(p, key);
}
} break;
case lskStorage: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 first, second;
qint32 size;
map.stream >> key >> first >> second >> size;
storageMap.insert(StorageKey(first, second), FileDesc(key, size));
storageFilesSize += size;
}
} break;
default:
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
return Local::ReadMapFailed;
}
if (map.stream.status() != QDataStream::Ok) {
LOG(("App Error: reading encrypted map bad status: %1").arg(map.stream.status()));
return Local::ReadMapFailed;
}
}
_draftsMap = draftsMap;
_draftsPositionsMap = draftsPositionsMap;
_draftsNotReadMap = draftsNotReadMap;
_storageMap = storageMap;
_storageFilesSize = storageFilesSize;
_mapChanged = false;
LOG(("Map read time: %1").arg(getms() - ms));
return Local::ReadMapDone;
}
enum WriteMapWhen {
WriteMapNow,
WriteMapFast,
WriteMapSoon,
};
void _writeMap(WriteMapWhen when = WriteMapSoon) {
if (when != WriteMapNow) {
_manager->writeMap(when == WriteMapFast);
return;
}
_manager->writingMap();
if (!_mapChanged) return;
if (_basePath.isEmpty()) {
LOG(("App Error: _basePath is empty in writeMap()"));
return;
}
QDir().mkpath(_basePath);
FileWriteDescriptor map(qsl("map"));
if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) {
uchar local5Key[LocalEncryptKeySize] = { 0 };
QByteArray pass(LocalEncryptKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized);
memset_rand(pass.data(), pass.size());
memset_rand(salt.data(), salt.size());
createLocalKey(pass, &salt, &_localKey);
_passKeySalt.resize(LocalEncryptSaltSize);
memset_rand(_passKeySalt.data(), _passKeySalt.size());
createLocalKey(QByteArray(), &_passKeySalt, &_passKey);
EncryptedDescriptor passKeyData(LocalEncryptKeySize);
_localKey.write(passKeyData.stream);
_passKeyEncrypted = map.prepareEncrypted(passKeyData, _passKey);
}
map.writeData(_passKeySalt);
map.writeData(_passKeyEncrypted);
uint32 mapSize = 0;
if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
if (!_draftsPositionsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsPositionsMap.size() * sizeof(quint64) * 2;
if (!_storageMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _storageMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
EncryptedDescriptor mapData(mapSize);
if (!_draftsMap.isEmpty()) {
mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_draftsPositionsMap.isEmpty()) {
mapData.stream << quint32(lskDraftPosition) << quint32(_draftsPositionsMap.size());
for (DraftsMap::const_iterator i = _draftsPositionsMap.cbegin(), e = _draftsPositionsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_storageMap.isEmpty()) {
mapData.stream << quint32(lskStorage) << quint32(_storageMap.size());
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
}
}
map.writeEncrypted(mapData);
map.finish();
_mapChanged = false;
}
}
namespace _local_inner {
Manager::Manager() {
_mapWriteTimer.setSingleShot(true);
connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout()));
}
void Manager::writeMap(bool fast) {
if (!_mapWriteTimer.isActive() || fast) {
_mapWriteTimer.start(fast ? 1 : WriteMapTimeout);
} else if (_mapWriteTimer.remainingTime() <= 0) {
mapWriteTimeout();
}
}
void Manager::writingMap() {
_mapWriteTimer.stop();
}
void Manager::mapWriteTimeout() {
_writeMap(WriteMapNow);
}
void Manager::finish() {
if (_mapWriteTimer.isActive()) {
mapWriteTimeout();
_mapWriteTimer.stop();
}
}
}
namespace Local {
mtpAuthKey &oldKey() {
return _oldKey;
}
void createOldKey(QByteArray *salt) {
createLocalKey(QByteArray(), salt, &_oldKey);
}
void start() {
if (!_started) {
_started = true;
_manager = new _local_inner::Manager();
}
}
void stop() {
if (_manager) {
_writeMap(WriteMapNow);
_manager->finish();
_manager->deleteLater();
_manager = 0;
}
}
ReadMapState readMap(const QByteArray &pass) {
ReadMapState result = _readMap(pass);
if (result == ReadMapFailed) {
_mapChanged = true;
_writeMap(WriteMapNow);
}
return result;
}
void writeDraft(const PeerId &peer, const QString &text) {
if (!_working()) return;
if (text.isEmpty()) {
DraftsMap::iterator i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
_draftsMap.erase(i);
_mapChanged = true;
_writeMap();
}
_draftsNotReadMap.remove(peer);
} else {
DraftsMap::const_iterator i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(quint32) + text.size() * sizeof(QChar));
data.stream << quint64(peer) << text;
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
_draftsNotReadMap.remove(peer);
}
}
QString readDraft(const PeerId &peer) {
if (!_draftsNotReadMap.remove(peer)) return QString();
DraftsMap::iterator j = _draftsMap.find(peer);
if (j == _draftsMap.cend()) {
return QString();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsMap.erase(j);
return QString();
}
quint64 draftPeer;
QString draftText;
draft.stream >> draftPeer >> draftText;
return (draftPeer == peer) ? draftText : QString();
}
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur) {
if (!_working()) return;
if (cur.position == 0 && cur.anchor == 0 && cur.scroll == 0) {
DraftsMap::iterator i = _draftsPositionsMap.find(peer);
if (i != _draftsPositionsMap.cend()) {
clearKey(i.value());
_draftsPositionsMap.erase(i);
_mapChanged = true;
_writeMap();
}
} else {
DraftsMap::const_iterator i = _draftsPositionsMap.constFind(peer);
if (i == _draftsPositionsMap.cend()) {
i = _draftsPositionsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3);
data.stream << quint64(peer) << qint32(cur.position) << qint32(cur.anchor) << qint32(cur.scroll);
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
}
}
MessageCursor readDraftPositions(const PeerId &peer) {
DraftsMap::iterator j = _draftsPositionsMap.find(peer);
if (j == _draftsPositionsMap.cend()) {
return MessageCursor();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsPositionsMap.erase(j);
return MessageCursor();
}
quint64 draftPeer;
qint32 curPosition, curAnchor, curScroll;
draft.stream >> draftPeer >> curPosition >> curAnchor >> curScroll;
return (draftPeer == peer) ? MessageCursor(curPosition, curAnchor, curScroll) : MessageCursor();
}
bool hasDraftPositions(const PeerId &peer) {
return (_draftsPositionsMap.constFind(peer) != _draftsPositionsMap.cend());
}
qint32 _storageImageSize(qint32 rawlen) {
// fulllen + storagekey + type + len + data
qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen;
if (result & 0x0F) result += 0x10 - (result & 0x0F);
result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5
return result;
}
void writeImage(const StorageKey &location, const ImagePtr &image) {
if (image->isNull() || !image->loaded()) return;
if (_storageMap.constFind(location) != _storageMap.cend()) return;
QByteArray fmt = image->savedFormat();
mtpTypeId format = 0;
if (fmt == "JPG") {
format = mtpc_storage_fileJpeg;
} else if (fmt == "PNG") {
format = mtpc_storage_filePng;
} else if (fmt == "GIF") {
format = mtpc_storage_fileGif;
}
if (format) {
image->forget();
writeImage(location, StorageImageSaved(format, image->savedData()), false);
}
}
void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) {
if (!_working()) return;
qint32 size = _storageImageSize(image.data.size());
StorageMap::const_iterator i = _storageMap.constFind(location);
if (i == _storageMap.cend()) {
i = _storageMap.insert(location, FileDesc(genKey(), size));
_storageFilesSize += size;
_mapChanged = true;
_writeMap();
} else if (!overwrite) {
return;
}
EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size());
data.stream << quint64(location.first) << quint64(location.second) << quint32(image.type) << image.data;
FileWriteDescriptor file(i.value().first, false);
file.writeEncrypted(data);
if (i.value().second != size) {
_storageFilesSize += size;
_storageFilesSize -= i.value().second;
_storageMap[location].second = size;
}
}
StorageImageSaved readImage(const StorageKey &location) {
StorageMap::iterator j = _storageMap.find(location);
if (j == _storageMap.cend()) {
return StorageImageSaved();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value().first), false)) {
clearKey(j.value().first, false);
_storageFilesSize -= j.value().second;
_storageMap.erase(j);
return StorageImageSaved();
}
QByteArray imageData;
quint64 locFirst, locSecond;
quint32 imageType;
draft.stream >> locFirst >> locSecond >> imageType >> imageData;
return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(imageType, imageData) : StorageImageSaved();
}
int32 hasImages() {
return _storageMap.size();
}
qint64 storageFilesSize() {
return _storageFilesSize;
}
struct ClearManagerData {
QThread *thread;
StorageMap images;
QMutex mutex;
QList<int> tasks;
bool working;
};
ClearManager::ClearManager() : data(new ClearManagerData()) {
data->thread = new QThread();
data->working = true;
}
bool ClearManager::addTask(int task) {
QMutexLocker lock(&data->mutex);
if (!data->working) return false;
if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true;
if (task == ClearManagerAll) {
data->tasks.clear();
} else {
if (task & ClearManagerImages) {
if (data->images.isEmpty()) {
data->images = _storageMap;
} else {
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
StorageKey k = i.key();
while (data->images.constFind(k) != data->images.cend()) {
++k.second;
}
data->images.insert(k, i.value());
}
}
_storageMap.clear();
_storageFilesSize = 0;
_mapChanged = true;
_writeMap();
}
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
}
data->tasks.push_back(task);
return true;
}
bool ClearManager::hasTask(ClearManagerTask task) {
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) return false;
if (data->tasks.at(0) == ClearManagerAll) return true;
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
return false;
}
void ClearManager::start() {
moveToThread(data->thread);
connect(data->thread, SIGNAL(started()), this, SLOT(onStart()));
data->thread->start();
}
ClearManager::~ClearManager() {
data->thread->deleteLater();
delete data;
}
void ClearManager::onStart() {
while (true) {
int task = 0;
bool result = false;
StorageMap images;
{
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) {
data->working = false;
break;
}
task = data->tasks.at(0);
images = data->images;
}
switch (task) {
case ClearManagerAll:
result = (QDir(cTempDir()).removeRecursively() && QDir(_basePath).removeRecursively());
break;
case ClearManagerDownloads:
result = QDir(cTempDir()).removeRecursively();
break;
case ClearManagerImages:
for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) {
clearKey(i.value().first, false);
}
result = true;
break;
}
{
QMutexLocker lock(&data->mutex);
if (data->tasks.at(0) == task) {
data->tasks.pop_front();
if (data->tasks.isEmpty()) {
data->working = false;
}
}
if (result) {
emit succeed(task, data->working ? 0 : this);
} else {
emit failed(task, data->working ? 0 : this);
}
if (!data->working) break;
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop messaging app, see https://telegram.org
Telegram Desktop is free 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.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
#include "types.h"
namespace _local_inner {
class Manager : public QObject {
Q_OBJECT
public:
Manager();
void writeMap(bool fast);
void writingMap();
void finish();
public slots:
void mapWriteTimeout();
private:
QTimer _mapWriteTimer;
};
}
namespace Local {
mtpAuthKey &oldKey();
void createOldKey(QByteArray *salt = 0);
void start();
void stop();
enum ClearManagerTask {
ClearManagerAll = 0xFFFF,
ClearManagerDownloads = 0x01,
ClearManagerImages = 0x02,
};
class ClearManagerData;
class ClearManager : public QObject {
Q_OBJECT
public:
ClearManager();
bool addTask(int task);
bool hasTask(ClearManagerTask task);
void start();
~ClearManager();
public slots:
void onStart();
signals:
void succeed(int task, void *manager);
void failed(int task, void *manager);
private:
ClearManagerData *data;
};
enum ReadMapState {
ReadMapFailed = 0,
ReadMapDone = 1,
ReadMapPassNeeded = 2,
};
ReadMapState readMap(const QByteArray &pass);
void writeDraft(const PeerId &peer, const QString &text);
QString readDraft(const PeerId &peer);
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur);
MessageCursor readDraftPositions(const PeerId &peer);
bool hasDraftPositions(const PeerId &peer);
void writeImage(const StorageKey &location, const ImagePtr &img);
void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
StorageImageSaved readImage(const StorageKey &location);
int32 hasImages();
qint64 storageFilesSize();
};

View File

@ -143,7 +143,7 @@ void TopBarWidget::enableShadow(bool enable) {
void TopBarWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
if (e->rect().top() < st::topBarHeight) {
if (e->rect().top() < st::topBarHeight) { // optimize shadow-only drawing
p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBG->b);
if (_clearSelection.isHidden()) {
p.save();
@ -154,8 +154,6 @@ void TopBarWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::btnDefLink.color->p);
p.drawText(st::topBarSelectedPos.x(), st::topBarSelectedPos.y() + st::linkFont->ascent, _selStr);
}
} else {
int a = 0; // optimize shadow-only drawing
}
if (_drawShadow) {
p.fillRect(st::titleShadow, st::topBarHeight, width() - st::titleShadow, st::titleShadow, st::titleShadowColor->b);
@ -1734,7 +1732,6 @@ void MainWidget::gotState(const MTPupdates_State &state) {
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
updInited = true;
dialogs.loadDialogs();
@ -1752,7 +1749,7 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) {
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
updInited = true;
} break;
case mtpc_updates_differenceSlice: {
@ -2000,6 +1997,8 @@ int32 MainWidget::dlgsWidth() const {
}
MainWidget::~MainWidget() {
if (App::main() == this) history.showPeer(0, 0, true);
delete hider;
MTP::clearGlobalHandlers();
App::deinitMedia(false);
@ -2043,7 +2042,6 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
handleUpdates(updates);
} catch(mtpErrorUnexpected &e) { // just some other type

View File

@ -468,7 +468,6 @@ void MediaView::showPhoto(PhotoData *photo) {
_doc = 0;
_zoom = 0;
MTP::clearLoaderPriorities();
_photo->full->load();
_full = -1;
_current = QPixmap();
_down = OverNone;
@ -490,6 +489,7 @@ void MediaView::showPhoto(PhotoData *photo) {
_width = _w;
_from = App::user(_photo->user);
updateControls();
_photo->full->load();
if (isHidden()) {
psUpdateOverlayed(this);
show();

View File

@ -18,6 +18,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "stdafx.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
typedef QMap<int32, MTProtoSessionPtr> Sessions;
Sessions sessions;
@ -61,8 +63,6 @@ namespace {
MTPSessionResetHandler sessionResetHandler = 0;
_mtp_internal::RequestResender *resender = 0;
mtpAuthKey _localKey;
void importDone(const MTPauth_Authorization &result, mtpRequestId req) {
QMutexLocker locker1(&requestByDCLock);
@ -564,31 +564,11 @@ namespace _mtp_internal {
};
namespace MTP {
mtpAuthKey &localKey() {
return _localKey;
}
void createLocalKey(const QByteArray &pass, QByteArray *salt) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
_localKey.setKey(key);
}
void start() {
unixtimeInit();
if (!localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("App Error: trying to start MTP without local key!"));
return;
}

View File

@ -63,9 +63,6 @@ namespace _mtp_internal {
namespace MTP {
mtpAuthKey &localKey();
void createLocalKey(const QByteArray &pass, QByteArray *salt = 0);
static const uint32 cfg = 1 * _mtp_internal::dcShift; // send(MTPhelp_GetConfig(), MTP::cfg + dc) - for dc enum
static const uint32 dld[MTPDownloadSessionsCount] = { // send(req, callbacks, MTP::dld[i] + dc) - for download
0x10 * _mtp_internal::dcShift,

View File

@ -48,7 +48,7 @@ public:
return _keyId;
}
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) {
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) const {
if (!_isset) throw mtpErrorKeyNotReady(QString("prepareAES(.., %1)").arg(logBool(send)));
uint32 x = send ? 0 : 8;
@ -112,14 +112,14 @@ inline void aesEncrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_ENCRYPT);
}
inline void aesEncrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesEncrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV);
return aesEncrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);
@ -136,14 +136,14 @@ inline void aesDecrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_DECRYPT);
}
inline void aesDecrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesDecrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV, false);
return aesDecrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);

View File

@ -707,15 +707,6 @@ void MTPautoConnection::tcpSend(mtpBuffer &buffer) {
TCP_LOG(("TCP Info: write %1 packet %2 bytes").arg(packetNum).arg(len));
sock.write((const char*)&buffer[0], len);
//int64 b = sock.bytesToWrite();
//if (b > 100000) {
// int a = 0;
//}
//sock.flush();
//int64 b2 = sock.bytesToWrite();
//if (b2 > 0) {
// TCP_LOG(("TCP Info: writing many, %1 left to write").arg(b2));
//}
}
void MTPautoConnection::httpSend(mtpBuffer &buffer) {
@ -1127,7 +1118,9 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()));
connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)));
connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()));
connect(this, SIGNAL(needToSendAsync()), sessionData->owner(), SIGNAL(needToSend()));
connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()));
connect(this, SIGNAL(sendAnythingAsync(quint64)), sessionData->owner(), SLOT(sendAnything(quint64)));
}
void MTProtoConnectionPrivate::onConfigLoaded() {
@ -1415,7 +1408,7 @@ void MTProtoConnectionPrivate::tryToSend() {
toSendPingId = 0;
} else {
int32 st = getState();
DEBUG_LOG(("MTP Info: trying to send after ping, state: %1").arg(st));
DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(dc).arg(st));
if (st != MTProtoConnection::Connected) {
return; // just do nothing, if is not connected yet
}
@ -1960,7 +1953,7 @@ void MTProtoConnectionPrivate::handleReceived() {
uint32 toAckSize = ackRequestData.size();
if (toAckSize) {
DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(logVectorLong(ackRequestData)));
sessionData->owner()->sendAnything(MTPAckSendWaiting);
emit sendAnythingAsync(MTPAckSendWaiting);
}
bool emitSignal = false;
@ -1988,7 +1981,7 @@ void MTProtoConnectionPrivate::handleReceived() {
if (!wasConnected) {
if (getState() == MTProtoConnection::Connected) {
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
}
}
}
@ -3199,7 +3192,7 @@ void MTProtoConnectionPrivate::authKeyCreated() {
toSendPingId = MTP::nonce<uint64>(); // get server_salt
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
// disconnect(&pinger, SIGNAL(timeout()), 0, 0);
// connect(&pinger, SIGNAL(timeout()), this, SLOT(sendPing()));

View File

@ -309,6 +309,8 @@ signals:
void needToRestart();
void stateChanged(qint32 newState);
void sessionResetDone();
void needToSendAsync();
void sendAnythingAsync(quint64);
public slots:

View File

@ -19,6 +19,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mtpDC.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
MTProtoDCMap gDCs;
@ -65,7 +67,7 @@ namespace {
QByteArray data, decrypted;
stream >> data;
if (!MTP::localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("MTP Error: reading encrypted keys without local key!"));
continue;
}
@ -77,7 +79,7 @@ namespace {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("MTP Error: bad decrypt key, data from user-config not decrypted"));
@ -271,7 +273,7 @@ namespace {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("MTP Info: keys file opened for writing %1 keys").arg(keysToWrite.size()));
QDataStream keysStream(&keysFile);

View File

@ -20,6 +20,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "window.h"
#include "application.h"
#include "localstorage.h"
namespace {
int32 _priority = 1;
@ -44,9 +45,9 @@ namespace {
}
mtpFileLoader::mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(0), volume(volume), local(local), secret(secret),
id(0), access(0), fileIsOpen(false), size(size), type(MTP_storage_fileUnknown()) {
id(0), access(0), fileIsOpen(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(dc);
if (i == queues.cend()) {
i = queues.insert(dc, mtpFileLoaderQueue());
@ -55,9 +56,9 @@ id(0), access(0), fileIsOpen(false), size(size), type(MTP_storage_fileUnknown())
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(false), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@ -66,9 +67,9 @@ id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size, bool todata) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@ -85,7 +86,7 @@ bool mtpFileLoader::done() const {
}
mtpTypeId mtpFileLoader::fileType() const {
return type.type();
return type;
}
const QByteArray &mtpFileLoader::bytes() const {
@ -130,7 +131,7 @@ void mtpFileLoader::loadNext() {
void mtpFileLoader::finishFail() {
bool started = currentOffset(true) > 0;
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (fileIsOpen) {
file.close();
@ -186,7 +187,7 @@ bool mtpFileLoader::loadPart() {
void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) {
Requests::iterator i = requests.find(req);
if (i == requests.cend()) return;
if (i == requests.cend()) return loadNext();
int32 limit = locationType ? DocumentDownloadPartSize : DownloadPartSize;
int32 dcIndex = i.value();
@ -239,7 +240,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
return finishFail();
}
}
type = d.vtype;
type = d.vtype.type();
complete = true;
if (fileIsOpen) {
file.close();
@ -249,10 +250,13 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
removeFromQueue();
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
if (!queue->queries && dcIndex) {
App::app()->killDownloadSessionsStart(dc);
}
if (!locationType && triedLocal && (fname.isEmpty() || duplicateInData)) {
Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(type, data));
}
}
emit progress(this);
loadNext();
@ -287,12 +291,38 @@ void mtpFileLoader::pause() {
void mtpFileLoader::start(bool loadFirst, bool prior) {
if (complete) return;
if (!locationType && !triedLocal) {
triedLocal = true;
StorageImageSaved cached = Local::readImage(storageKey(dc, volume, local));
if (cached.type != mtpc_storage_fileUnknown) {
data = cached.data;
if (!fname.isEmpty() && duplicateInData) {
if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
if (file.write(data) != qint64(data.size())) {
return finishFail();
}
}
type = cached.type;
complete = true;
if (fileIsOpen) {
file.close();
fileIsOpen = false;
psPostprocessFile(QFileInfo(file).absoluteFilePath());
}
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
emit progress(this);
return loadNext();
}
}
if (!fname.isEmpty() && !duplicateInData && !fileIsOpen) {
fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
finishFail();
return;
return finishFail();
}
}
@ -386,7 +416,7 @@ void mtpFileLoader::start(bool loadFirst, bool prior) {
void mtpFileLoader::cancel() {
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (fileIsOpen) {
file.close();

View File

@ -60,7 +60,7 @@ signals:
private:
mtpFileLoaderQueue *queue;
bool inQueue, complete;
bool inQueue, complete, triedLocal;
void cancelRequests();
@ -96,6 +96,6 @@ private:
QByteArray data;
int32 size;
MTPstorage_FileType type;
mtpTypeId type;
};

View File

@ -83,9 +83,6 @@ void MTProtoSession::start(int32 dcenter, uint32 connects) {
timeouter.start(1000);
connect(&sender, SIGNAL(timeout()), this, SIGNAL(needToSend()));
connect(this, SIGNAL(startSendTimer(int)), &sender, SLOT(start(int)));
connect(this, SIGNAL(stopSendTimer()), &sender, SLOT(stop()));
connect(this, SIGNAL(needToSendAsync()), this, SIGNAL(needToSend()));
MTProtoDCMap &dcs(mtpDCMap());
@ -135,7 +132,7 @@ void MTProtoSession::stop() {
}
}
void MTProtoSession::sendAnything(uint64 msCanWait) {
void MTProtoSession::sendAnything(quint64 msCanWait) {
uint64 ms = getms(true);
if (msSendCall) {
if (ms > msSendCall + msWait) {
@ -150,13 +147,14 @@ void MTProtoSession::sendAnything(uint64 msCanWait) {
msWait = msCanWait;
}
if (msWait) {
DEBUG_LOG(("MTP Info: dc %1 can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
msSendCall = ms;
emit startSendTimer(msWait);
DEBUG_LOG(("MTP Info: can wait for %1ms from current %2").arg(msWait).arg(msSendCall));
sender.start(msWait);
} else {
emit stopSendTimer();
DEBUG_LOG(("MTP Info: dc %1 stopped send timer, can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
sender.stop();
msSendCall = 0;
emit needToSendAsync();
emit needToSend();
}
}

View File

@ -235,7 +235,6 @@ public:
template <typename TRequest>
mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, bool needsLayer = false, bool toMainDC = false, mtpRequestId after = 0); // send mtp request
void sendAnything(uint64 msCanWait);
void cancel(mtpRequestId requestId, mtpMsgId msgId);
int32 requestState(mtpRequestId requestId) const;
@ -253,10 +252,6 @@ signals:
void authKeyCreated();
void needToSend();
void needToSendAsync(); // emit this signal, to emit needToSend() in MTProtoSession thread
void startSendTimer(int msec); // manipulating timer from all threads
void stopSendTimer();
public slots:
@ -268,6 +263,8 @@ public slots:
void onConnectionStateChange(qint32 newState);
void onResetDone();
void sendAnything(quint64 msCanWait);
private:
typedef QList<MTProtoConnection*> MTProtoConnections;

View File

@ -367,7 +367,7 @@ void ProfileInner::reorderParticipants() {
} else {
_participants.clear();
if (_peerUser) {
_onlineText = App::onlineText(_peerUser->onlineTill, t, true);
_onlineText = App::onlineText(_peerUser, t, true);
} else {
_onlineText = lang(lng_chat_no_members);
}
@ -520,7 +520,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
if (!data) {
data = _participantsData[cnt] = new ParticipantData();
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, l_time);
data->online = App::onlineText(user, l_time);
data->cankick = (user != App::self()) && (_chatAdmin || (_peerChat->cankick.constFind(user) != _peerChat->cankick.cend()));
}
p.setPen(st::profileListNameColor->p);

View File

@ -31,6 +31,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/usernamebox.h"
#include "gui/filedialog.h"
#include "localstorage.h"
Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent),
_count(count), _sel(snap(sel, 0, _count)), _wasSel(_sel), _st(st), _pressed(false) {
resize(_st.width, _st.bar.pxHeight());
@ -154,6 +156,12 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
_catsAndDogs(this, lang(lng_settings_cats_and_dogs), cCatsAndDogs()),
// local storage
_localImagesClear(this, lang(lng_local_images_clear)),
_imagesClearingWidth(st::linkFont->m.width(lang(lng_local_images_clearing))),
_imagesClearedWidth(st::linkFont->m.width(lang(lng_local_images_cleared))),
_imagesClearFailedWidth(st::linkFont->m.width(lang(lng_local_images_clear_failed))),
// advanced
_connectionType(this, lang(lng_connection_auto)),
_resetSessions(this, lang(lng_settings_reset)),
@ -231,11 +239,19 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
case Window::TempDirExists: _tempDirClearState = TempDirExists; break;
case Window::TempDirRemoving: _tempDirClearState = TempDirClearing; break;
}
connect(App::wnd(), SIGNAL(tempDirCleared()), this, SLOT(onTempDirCleared()));
connect(App::wnd(), SIGNAL(tempDirClearFailed()), this, SLOT(onTempDirClearFailed()));
connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int)));
connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int)));
connect(&_catsAndDogs, SIGNAL(changed()), this, SLOT(onCatsAndDogs()));
// local storage
connect(&_localImagesClear, SIGNAL(clicked()), this, SLOT(onLocalImagesClear()));
switch (App::wnd()->localImagesState()) {
case Window::TempDirEmpty: _imagesClearState = TempDirEmpty; break;
case Window::TempDirExists: _imagesClearState = TempDirExists; break;
case Window::TempDirRemoving: _imagesClearState = TempDirClearing; break;
}
// advanced
connect(&_connectionType, SIGNAL(clicked()), this, SLOT(onConnectionType()));
connect(&_resetSessions, SIGNAL(clicked()), this, SLOT(onResetSessions()));
@ -456,14 +472,40 @@ void SettingsInner::paintEvent(QPaintEvent *e) {
top += st::setSectionSkip;
top += _catsAndDogs.height();
// local storage
p.setFont(st::setHeaderFont->f);
p.setPen(st::setHeaderColor->p);
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_cache));
top += st::setHeaderSkip;
QString localImagesText = lang(lng_settings_no_images_cached);
int32 cnt = Local::hasImages();
if (cnt) {
localImagesText = lang((cnt > 1) ? lng_settings_images_cached : lng_settings_image_cached).replace(qsl("{count}"), QString::number(cnt)).replace(qsl("{size}"), formatSizeText(Local::storageFilesSize()));
}
p.setFont(st::linkFont->f);
p.setPen(st::black->p);
p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, localImagesText);
QString clearText;
int32 clearWidth = 0;
switch (_imagesClearState) {
case TempDirClearing: clearText = lang(lng_local_images_clearing); clearWidth = _imagesClearingWidth; break;
case TempDirCleared: clearText = lang(lng_local_images_cleared); clearWidth = _imagesClearedWidth; break;
case TempDirClearFailed: clearText = lang(lng_local_images_clear_failed); clearWidth = _imagesClearFailedWidth; break;
}
if (clearWidth) {
p.drawText(_left + st::setWidth - clearWidth, top + st::linkFont->ascent, clearText);
}
top += _localImagesClear.height();
}
// advanced
p.setFont(st::setHeaderFont->f);
p.setPen(st::setHeaderColor->p);
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_advanced));
top += st::setHeaderSkip;
p.setFont(st::linkFont->f);
p.setPen(st::black->p);
p.drawText(_left + st::setHeaderLeft, _connectionType.y() + st::linkFont->ascent, _connectionTypeText);
@ -541,6 +583,10 @@ void SettingsInner::resizeEvent(QResizeEvent *e) {
}
top += st::setSectionSkip;
_catsAndDogs.move(_left, top); top += _catsAndDogs.height();
// local storage
top += st::setHeaderSkip;
_localImagesClear.move(_left + st::setWidth - _localImagesClear.width(), top); top += _localImagesClear.height();
}
// advanced
@ -754,6 +800,7 @@ void SettingsInner::showAll() {
_downloadPathClear.hide();
}
}
} else {
_replaceEmojis.hide();
_viewEmojis.hide();
@ -763,6 +810,14 @@ void SettingsInner::showAll() {
_dontAskDownloadPath.hide();
_downloadPathEdit.hide();
_downloadPathClear.hide();
_localImagesClear.hide();
}
// local storage
if (self() && _imagesClearState == TempDirExists) {
_localImagesClear.show();
} else {
_localImagesClear.hide();
}
// advanced
@ -1103,20 +1158,35 @@ void SettingsInner::onDownloadPathClear() {
void SettingsInner::onDownloadPathClearSure() {
App::wnd()->hideLayer();
App::wnd()->tempDirDelete();
App::wnd()->tempDirDelete(Local::ClearManagerDownloads);
_tempDirClearState = TempDirClearing;
showAll();
update();
}
void SettingsInner::onTempDirCleared() {
_tempDirClearState = TempDirCleared;
void SettingsInner::onLocalImagesClear() {
App::wnd()->tempDirDelete(Local::ClearManagerImages);
_imagesClearState = TempDirClearing;
showAll();
update();
}
void SettingsInner::onTempDirClearFailed() {
_tempDirClearState = TempDirClearFailed;
void SettingsInner::onTempDirCleared(int task) {
if (task & Local::ClearManagerDownloads) {
_tempDirClearState = TempDirCleared;
} else if (task & Local::ClearManagerImages) {
_imagesClearState = TempDirCleared;
}
showAll();
update();
}
void SettingsInner::onTempDirClearFailed(int task) {
if (task & Local::ClearManagerDownloads) {
_tempDirClearState = TempDirClearFailed;
} else if (task & Local::ClearManagerImages) {
_imagesClearState = TempDirClearFailed;
}
showAll();
update();
}

View File

@ -122,11 +122,13 @@ public slots:
void onDownloadPathEdited();
void onDownloadPathClear();
void onDownloadPathClearSure();
void onTempDirCleared();
void onTempDirClearFailed();
void onTempDirCleared(int task);
void onTempDirClearFailed(int task);
void onCatsAndDogs();
void onLocalImagesClear();
void onUpdateChecking();
void onUpdateLatest();
void onUpdateDownloading(qint64 ready, qint64 total);
@ -212,6 +214,11 @@ private:
TempDirClearState _tempDirClearState;
FlatCheckbox _catsAndDogs;
// local storage
LinkButton _localImagesClear;
int32 _imagesClearingWidth, _imagesClearedWidth, _imagesClearFailedWidth;
TempDirClearState _imagesClearState;
// advanced
LinkButton _connectionType, _resetSessions;
FlatButton _logOut;

View File

@ -17,6 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#include "stdafx.h"
#include "application.h"
#ifdef Q_OS_WIN
#elif defined Q_OS_MAC
#include <mach/mach_time.h>
@ -216,7 +218,7 @@ bool checkms() {
_msAddToUnixtime = ((ms - unixms) / 1000LL) * 1000LL;
} else if (unixms > ms + 1000LL) {
_msAddToMsStart += ((unixms - ms) / 1000LL) * 1000LL;
adjustSingleTimers();
if (App::app()) emit App::app()->adjustSingleTimers();
return true;
}
return false;
@ -243,27 +245,24 @@ uint64 getms(bool checked) {
#endif
}
namespace {
QSet<SingleTimer*> _activeSingleTimers;
QMutex _activeSingleTimersMutex;
}
void regSingleTimer(SingleTimer *timer) {
QMutexLocker lock(&_activeSingleTimersMutex);
_activeSingleTimers.insert(timer);
}
void unregSingleTimer(SingleTimer *timer) {
QMutexLocker lock(&_activeSingleTimersMutex);
_activeSingleTimers.remove(timer);
}
void adjustSingleTimers() {
for (QSet<SingleTimer*>::const_iterator i = _activeSingleTimers.cbegin(), e = _activeSingleTimers.cend(); i != e; ++i) {
emit (*i)->callAdjust();
SingleTimer::SingleTimer() : _finishing(0), _inited(false) {
QTimer::setSingleShot(true);
if (App::app()) {
connect(App::app(), SIGNAL(adjustSingleTimers()), this, SLOT(adjust()));
_inited = true;
}
}
void SingleTimer::start(int msec) {
_finishing = getms(true) + (msec < 0 ? 0 : uint64(msec));
if (!_inited && App::app()) {
connect(App::app(), SIGNAL(adjustSingleTimers()), this, SLOT(adjust()));
_inited = true;
}
QTimer::start(msec);
}
uint64 msgid() {
#ifdef Q_OS_WIN
LARGE_INTEGER li;

View File

@ -101,45 +101,19 @@ inline void mylocaltime(struct tm * _Tm, const time_t * _Time) {
bool checkms(); // returns true if time has changed
uint64 getms(bool checked = false);
class SingleTimer;
void regSingleTimer(SingleTimer *timer);
void unregSingleTimer(SingleTimer *timer);
void adjustSingleTimers();
class SingleTimer : public QTimer { // single shot timer with check
Q_OBJECT
public:
SingleTimer() : _finishing(0) {
QTimer::setSingleShot(true);
connect(this, SIGNAL(callAdjust()), this, SLOT(adjust()));
connect(this, SIGNAL(timeout()), this, SLOT(unreg()));
}
void start(int msec) {
_finishing = getms(true) + (msec < 0 ? 0 : uint64(msec));
QTimer::start(msec);
regSingleTimer(this);
}
void stop() {
QTimer::stop();
unreg();
}
SingleTimer();
void setSingleShot(bool); // is not available
void start(); // is not available
~SingleTimer() {
unreg();
}
signals:
void callAdjust();
public slots:
void start(int msec);
void adjust() {
uint64 n = getms(true);
if (isActive()) {
@ -150,12 +124,10 @@ public slots:
}
}
}
void unreg() {
unregSingleTimer(this);
}
private:
uint64 _finishing;
bool _inited;
};

View File

@ -31,6 +31,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/confirmbox.h"
#include "mediaview.h"
#include "localstorage.h"
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : QWidget(parent), _shadow(st::boxShadow), _reconnect(this, QString()) {
set(text, reconnect);
@ -66,20 +67,6 @@ void ConnectingWidget::onReconnect() {
MTP::restart();
}
TempDirDeleter::TempDirDeleter(QThread *thread) {
moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(onStart()));
}
void TempDirDeleter::onStart() {
if (QDir(cTempDir()).removeRecursively()) {
emit succeed();
} else {
emit failed();
}
}
NotifyWindow::NotifyWindow(HistoryItem *msg, int32 x, int32 y) : history(msg->history()), item(msg)
#ifdef Q_OS_WIN
, started(GetTickCount())
@ -338,7 +325,7 @@ NotifyWindow::~NotifyWindow() {
Window::Window(QWidget *parent) : PsMainWindow(parent),
intro(0), main(0), settings(0), layerBG(0), _topWidget(0),
_connecting(0), _tempDeleter(0), _tempDeleterThread(0), dragging(false), _inactivePress(false), _mediaView(0) {
_connecting(0), _clearManager(0), dragging(false), _inactivePress(false), _mediaView(0) {
icon16 = icon256.scaledToWidth(16, Qt::SmoothTransformation);
icon32 = icon256.scaledToWidth(32, Qt::SmoothTransformation);
@ -944,40 +931,56 @@ void Window::resizeEvent(QResizeEvent *e) {
}
Window::TempDirState Window::tempDirState() {
if (_tempDeleter) {
if (_clearManager && _clearManager->hasTask(Local::ClearManagerDownloads)) {
return TempDirRemoving;
}
return QDir(cTempDir()).exists() ? TempDirExists : TempDirEmpty;
}
void Window::tempDirDelete() {
if (_tempDeleter) return;
_tempDeleterThread = new QThread();
_tempDeleter = new TempDirDeleter(_tempDeleterThread);
connect(_tempDeleter, SIGNAL(succeed()), this, SLOT(onTempDirCleared()));
connect(_tempDeleter, SIGNAL(failed()), this, SLOT(onTempDirClearFailed()));
_tempDeleterThread->start();
Window::TempDirState Window::localImagesState() {
if (_clearManager && _clearManager->hasTask(Local::ClearManagerImages)) {
return TempDirRemoving;
}
return Local::hasImages() ? TempDirExists : TempDirEmpty;
}
void Window::onTempDirCleared() {
_tempDeleter->deleteLater();
_tempDeleter = 0;
_tempDeleterThread->deleteLater();
_tempDeleterThread = 0;
emit tempDirCleared();
void Window::tempDirDelete(int task) {
if (_clearManager) {
if (_clearManager->addTask(task)) {
return;
} else {
_clearManager->deleteLater();
_clearManager = 0;
}
}
_clearManager = new Local::ClearManager();
_clearManager->addTask(task);
connect(_clearManager, SIGNAL(succeed(int,void*)), this, SLOT(onClearFinished(int,void*)));
connect(_clearManager, SIGNAL(failed(int,void*)), this, SLOT(onClearFailed(int,void*)));
_clearManager->start();
}
void Window::onTempDirClearFailed() {
_tempDeleter->deleteLater();
_tempDeleter = 0;
_tempDeleterThread->deleteLater();
_tempDeleterThread = 0;
emit tempDirClearFailed();
void Window::onClearFinished(int task, void *manager) {
if (manager && manager == _clearManager) {
_clearManager->deleteLater();
_clearManager = 0;
}
emit tempDirCleared(task);
}
void Window::onClearFailed(int task, void *manager) {
if (manager && manager == _clearManager) {
_clearManager->deleteLater();
_clearManager = 0;
}
emit tempDirClearFailed(task);
}
void Window::quit() {
delete _mediaView;
_mediaView = 0;
delete main;
main = 0;
notifyClearFast();
}
@ -1405,8 +1408,7 @@ void Window::changingMsgId(HistoryItem *row, MsgId newId) {
Window::~Window() {
notifyClearFast();
delete _tempDeleter;
delete _tempDeleterThread;
delete _clearManager;
delete _connecting;
delete _mediaView;
delete trayIcon;

View File

@ -29,6 +29,9 @@ class MainWidget;
class SettingsWidget;
class BackgroundWidget;
class LayeredWidget;
namespace Local {
class ClearManager;
}
class ConnectingWidget : public QWidget {
Q_OBJECT
@ -52,21 +55,6 @@ private:
};
class TempDirDeleter : public QObject {
Q_OBJECT
public:
TempDirDeleter(QThread *thread);
public slots:
void onStart();
signals:
void succeed();
void failed();
};
class NotifyWindow : public QWidget, public Animated {
Q_OBJECT
@ -205,7 +193,8 @@ public:
TempDirEmpty,
};
TempDirState tempDirState();
void tempDirDelete();
TempDirState localImagesState();
void tempDirDelete(int task);
void quit();
@ -243,8 +232,8 @@ public slots:
void onInactiveTimer();
void onTempDirCleared();
void onTempDirClearFailed();
void onClearFinished(int task, void *manager);
void onClearFailed(int task, void *manager);
void notifyFire();
void updateTrayMenu(bool force = false);
@ -257,8 +246,8 @@ public slots:
signals:
void resized(const QSize &size);
void tempDirCleared();
void tempDirClearFailed();
void tempDirCleared(int task);
void tempDirClearFailed(int task);
protected:
@ -281,8 +270,7 @@ private:
QWidget *_topWidget; // temp hack for CountrySelect
ConnectingWidget *_connecting;
TempDirDeleter *_tempDeleter;
QThread *_tempDeleterThread;
Local::ClearManager *_clearManager;
void clearWidgets();

View File

@ -11,7 +11,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.6.11</string>
<string>0.6.12</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NOTE</key>

View File

@ -93,6 +93,7 @@ SOURCES += \
./SourceFiles/overviewwidget.cpp \
./SourceFiles/profilewidget.cpp \
./SourceFiles/localimageloader.cpp \
./SourceFiles/localstorage.cpp \
./SourceFiles/logs.cpp \
./SourceFiles/mainwidget.cpp \
./SourceFiles/settings.cpp \
@ -168,6 +169,7 @@ HEADERS += \
./SourceFiles/overviewwidget.h \
./SourceFiles/profilewidget.h \
./SourceFiles/localimageloader.h \
./SourceFiles/localstorage.h \
./SourceFiles/logs.h \
./SourceFiles/mainwidget.h \
./SourceFiles/settings.h \

Binary file not shown.

View File

@ -270,6 +270,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_localstorage.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_mainwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -486,6 +490,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_localstorage.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_mainwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -711,6 +719,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_localstorage.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_mainwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -852,6 +864,7 @@
<ClCompile Include="SourceFiles\langloaderplain.cpp" />
<ClCompile Include="SourceFiles\layerwidget.cpp" />
<ClCompile Include="SourceFiles\localimageloader.cpp" />
<ClCompile Include="SourceFiles\localstorage.cpp" />
<ClCompile Include="SourceFiles\logs.cpp" />
<ClCompile Include="SourceFiles\main.cpp" />
<ClCompile Include="SourceFiles\mainwidget.cpp" />
@ -1502,6 +1515,20 @@
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<ClInclude Include="SourceFiles\langloaderplain.h" />
<CustomBuild Include="SourceFiles\localstorage.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing localstorage.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/localstorage.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.3.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.3.1\QtGui"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing localstorage.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/localstorage.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.3.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.3.1\QtGui"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing localstorage.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/localstorage.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.3.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.3.1\QtGui"</Command>
</CustomBuild>
<ClInclude Include="SourceFiles\logs.h" />
<CustomBuild Include="SourceFiles\mtproto\mtpConnection.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing mtpConnection.h...</Message>

View File

@ -752,6 +752,18 @@
<ClCompile Include="GeneratedFiles\Release\moc_types.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\localstorage.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_localstorage.cpp">
<Filter>Generated Files\Deploy</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_localstorage.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_localstorage.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SourceFiles\stdafx.h">
@ -1005,6 +1017,9 @@
<CustomBuild Include="SourceFiles\types.h">
<Filter>Source Files</Filter>
</CustomBuild>
<CustomBuild Include="SourceFiles\localstorage.h">
<Filter>Source Files</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="SourceFiles\art\iconround256.ico" />

View File

@ -1521,7 +1521,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.6.11;
CURRENT_PROJECT_VERSION = 0.6.12;
DEBUG_INFORMATION_FORMAT = dwarf;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
@ -1539,7 +1539,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = 0.6.11;
CURRENT_PROJECT_VERSION = 0.6.12;
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
GCC_OPTIMIZATION_LEVEL = fast;
GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@ -1565,10 +1565,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.6.11;
CURRENT_PROJECT_VERSION = 0.6.12;
DEBUG_INFORMATION_FORMAT = dwarf;
DYLIB_COMPATIBILITY_VERSION = 0.6;
DYLIB_CURRENT_VERSION = 0.6.11;
DYLIB_CURRENT_VERSION = 0.6.12;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@ -1708,10 +1708,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 0.6.11;
CURRENT_PROJECT_VERSION = 0.6.12;
DEBUG_INFORMATION_FORMAT = dwarf;
DYLIB_COMPATIBILITY_VERSION = 0.6;
DYLIB_CURRENT_VERSION = 0.6.11;
DYLIB_CURRENT_VERSION = 0.6.12;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;