diff --git a/Telegram/DeployLinux.sh b/Telegram/DeployLinux.sh
index 6b7c5e1dc..8e3d2b69a 100755
--- a/Telegram/DeployLinux.sh
+++ b/Telegram/DeployLinux.sh
@@ -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!";
diff --git a/Telegram/DeployLinux32.sh b/Telegram/DeployLinux32.sh
index a854aed8e..653b71f94 100755
--- a/Telegram/DeployLinux32.sh
+++ b/Telegram/DeployLinux32.sh
@@ -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!"
diff --git a/Telegram/DeployMacWin.sh b/Telegram/DeployMacWin.sh
index acf1d1311..a62c5b23a 100755
--- a/Telegram/DeployMacWin.sh
+++ b/Telegram/DeployMacWin.sh
@@ -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!"
diff --git a/Telegram/DeployWin.sh b/Telegram/DeployWin.sh
index 490a2257b..f020dfad1 100644
--- a/Telegram/DeployWin.sh
+++ b/Telegram/DeployWin.sh
@@ -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!"
diff --git a/Telegram/PrepareLinux.sh b/Telegram/PrepareLinux.sh
index 9c459b8b7..c23dec9f7 100755
--- a/Telegram/PrepareLinux.sh
+++ b/Telegram/PrepareLinux.sh
@@ -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!"
diff --git a/Telegram/PrepareLinux32.sh b/Telegram/PrepareLinux32.sh
index 273c89e13..5141fbf8e 100755
--- a/Telegram/PrepareLinux32.sh
+++ b/Telegram/PrepareLinux32.sh
@@ -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!"
diff --git a/Telegram/PrepareMac.sh b/Telegram/PrepareMac.sh
index 3b29a3619..5bc207d64 100755
--- a/Telegram/PrepareMac.sh
+++ b/Telegram/PrepareMac.sh
@@ -1,5 +1,5 @@
-AppVersionStr=0.6.11
-AppVersion=6011
+AppVersionStr=0.6.12
+AppVersion=6012
 
 echo ""
 echo "Preparing version $AppVersionStr.."
diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat
index 4e508f9d3..1bebd5525 100644
--- a/Telegram/PrepareWin.bat
+++ b/Telegram/PrepareWin.bat
@@ -1,6 +1,6 @@
 @echo OFF
 
-set "AppVersionStr=0.6.11"
+set "AppVersionStr=0.6.12"
 echo.
 echo Preparing version %AppVersionStr%..
 echo.
diff --git a/Telegram/Resources/lang.txt b/Telegram/Resources/lang.txt
index 4b5e00eca..ed7728c45 100644
--- a/Telegram/Resources/lang.txt
+++ b/Telegram/Resources/lang.txt
@@ -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..)";
diff --git a/Telegram/Setup.iss b/Telegram/Setup.iss
index a2e31eca8..7ea4ed935 100644
--- a/Telegram/Setup.iss
+++ b/Telegram/Setup.iss
@@ -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"
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 9db1120c5..23484b676 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -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;
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index 587656aee..0b5fa18a1 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -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);
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index 2b2306fdb..c47573876 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -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() {
diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h
index 05266a421..84f8051a5 100644
--- a/Telegram/SourceFiles/application.h
+++ b/Telegram/SourceFiles/application.h
@@ -80,6 +80,8 @@ signals:
 	void peerPhotoDone(PeerId peer);
 	void peerPhotoFail(PeerId peer);
 
+	void adjustSingleTimers();
+
 public slots:
 
 	void startUpdateCheck(bool forceWait = false);
diff --git a/Telegram/SourceFiles/boxes/addparticipantbox.cpp b/Telegram/SourceFiles/boxes/addparticipantbox.cpp
index c1e02dffe..9dd608367 100644
--- a/Telegram/SourceFiles/boxes/addparticipantbox.cpp
+++ b/Telegram/SourceFiles/boxes/addparticipantbox.cpp
@@ -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();
 		}
diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp
index f7a31bfce..c98c83c4e 100644
--- a/Telegram/SourceFiles/boxes/contactsbox.cpp
+++ b/Telegram/SourceFiles/boxes/contactsbox.cpp
@@ -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();
 		}
diff --git a/Telegram/SourceFiles/boxes/newgroupbox.cpp b/Telegram/SourceFiles/boxes/newgroupbox.cpp
index 72c68509c..ad6674371 100644
--- a/Telegram/SourceFiles/boxes/newgroupbox.cpp
+++ b/Telegram/SourceFiles/boxes/newgroupbox.cpp
@@ -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();
 		}
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 6fa2f9959..66b74e607 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -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 } };
diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp
index 59fb5d235..f2f6a8e83 100644
--- a/Telegram/SourceFiles/gui/images.cpp
+++ b/Telegram/SourceFiles/gui/images.cpp
@@ -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);
diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h
index edddac9ad..4d5c27557 100644
--- a/Telegram/SourceFiles/gui/images.h
+++ b/Telegram/SourceFiles/gui/images.h
@@ -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();
 		}
 	}
 
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 516a07195..3018a9721 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -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);
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 0675e8c61..ff268a74a 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -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;
 
 };
 
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index 587cc8859..45be7fde4 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -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() {
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 58c9f3d14..f1adad33b 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -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;
+
 };
 
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
new file mode 100644
index 000000000..c10775434
--- /dev/null
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -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;
+			}
+		}
+	}
+
+}
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
new file mode 100644
index 000000000..47dbcf8df
--- /dev/null
+++ b/Telegram/SourceFiles/localstorage.h
@@ -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();
+
+};
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 084f3e4d0..34cca9118 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -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
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 421957620..cbdd8ca5e 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -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();
diff --git a/Telegram/SourceFiles/mtproto/mtp.cpp b/Telegram/SourceFiles/mtproto/mtp.cpp
index 06ee2b54d..0d1122de4 100644
--- a/Telegram/SourceFiles/mtproto/mtp.cpp
+++ b/Telegram/SourceFiles/mtproto/mtp.cpp
@@ -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;
 		}
diff --git a/Telegram/SourceFiles/mtproto/mtp.h b/Telegram/SourceFiles/mtproto/mtp.h
index ae0f39b2f..7aa6d2864 100644
--- a/Telegram/SourceFiles/mtproto/mtp.h
+++ b/Telegram/SourceFiles/mtproto/mtp.h
@@ -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,
diff --git a/Telegram/SourceFiles/mtproto/mtpAuthKey.h b/Telegram/SourceFiles/mtproto/mtpAuthKey.h
index 61909a03b..67e6c454c 100644
--- a/Telegram/SourceFiles/mtproto/mtpAuthKey.h
+++ b/Telegram/SourceFiles/mtproto/mtpAuthKey.h
@@ -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);
 
diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.cpp b/Telegram/SourceFiles/mtproto/mtpConnection.cpp
index 81b57745c..5ed9f7419 100644
--- a/Telegram/SourceFiles/mtproto/mtpConnection.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpConnection.cpp
@@ -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()));
diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.h b/Telegram/SourceFiles/mtproto/mtpConnection.h
index 313e46e2e..4d0e99426 100644
--- a/Telegram/SourceFiles/mtproto/mtpConnection.h
+++ b/Telegram/SourceFiles/mtproto/mtpConnection.h
@@ -309,6 +309,8 @@ signals:
 	void needToRestart();
 	void stateChanged(qint32 newState);
 	void sessionResetDone();
+	void needToSendAsync();
+	void sendAnythingAsync(quint64);
 
 public slots:
 
diff --git a/Telegram/SourceFiles/mtproto/mtpDC.cpp b/Telegram/SourceFiles/mtproto/mtpDC.cpp
index ad6152287..2d19de2aa 100644
--- a/Telegram/SourceFiles/mtproto/mtpDC.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpDC.cpp
@@ -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);
diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
index 62d12d961..4d7145921 100644
--- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
@@ -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();
diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.h b/Telegram/SourceFiles/mtproto/mtpFileLoader.h
index 99781f694..709b0ba2a 100644
--- a/Telegram/SourceFiles/mtproto/mtpFileLoader.h
+++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.h
@@ -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;
 
 };
diff --git a/Telegram/SourceFiles/mtproto/mtpSession.cpp b/Telegram/SourceFiles/mtproto/mtpSession.cpp
index 972ec83d0..b58fe7652 100644
--- a/Telegram/SourceFiles/mtproto/mtpSession.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpSession.cpp
@@ -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();
 	}
 }
 
diff --git a/Telegram/SourceFiles/mtproto/mtpSession.h b/Telegram/SourceFiles/mtproto/mtpSession.h
index 7d970cd43..372d8a642 100644
--- a/Telegram/SourceFiles/mtproto/mtpSession.h
+++ b/Telegram/SourceFiles/mtproto/mtpSession.h
@@ -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;
diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp
index 8e781e257..bf8a071b0 100644
--- a/Telegram/SourceFiles/profilewidget.cpp
+++ b/Telegram/SourceFiles/profilewidget.cpp
@@ -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);
diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp
index 119f11604..5f1a3ad39 100644
--- a/Telegram/SourceFiles/settingswidget.cpp
+++ b/Telegram/SourceFiles/settingswidget.cpp
@@ -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();
 }
diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h
index be325756d..8f96b382d 100644
--- a/Telegram/SourceFiles/settingswidget.h
+++ b/Telegram/SourceFiles/settingswidget.h
@@ -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;
diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/types.cpp
index fb7560630..d40e9d9f1 100644
--- a/Telegram/SourceFiles/types.cpp
+++ b/Telegram/SourceFiles/types.cpp
@@ -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;
diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h
index 25777d570..342e96fb6 100644
--- a/Telegram/SourceFiles/types.h
+++ b/Telegram/SourceFiles/types.h
@@ -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;
 
 };
 
diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp
index 6adc30ebf..c7aecc906 100644
--- a/Telegram/SourceFiles/window.cpp
+++ b/Telegram/SourceFiles/window.cpp
@@ -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;
diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h
index 97e28baf0..e01bd4c06 100644
--- a/Telegram/SourceFiles/window.h
+++ b/Telegram/SourceFiles/window.h
@@ -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();
 
diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist
index fd626455d..2fabc8058 100644
--- a/Telegram/Telegram.plist
+++ b/Telegram/Telegram.plist
@@ -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>
diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro
index 1f4c58aa7..25acb49d1 100644
--- a/Telegram/Telegram.pro
+++ b/Telegram/Telegram.pro
@@ -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 \
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index 25fb32ece..77ad32607 100644
Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ
diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj
index 2930574eb..98b7965a2 100644
--- a/Telegram/Telegram.vcxproj
+++ b/Telegram/Telegram.vcxproj
@@ -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>
diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters
index 52a875289..c06ca8714 100644
--- a/Telegram/Telegram.vcxproj.filters
+++ b/Telegram/Telegram.vcxproj.filters
@@ -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" />
diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj
index bd4359d8b..184a35647 100644
--- a/Telegram/Telegram.xcodeproj/project.pbxproj
+++ b/Telegram/Telegram.xcodeproj/project.pbxproj
@@ -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;