diff --git a/Telegram/SourceFiles/boxes/local_storage_box.cpp b/Telegram/SourceFiles/boxes/local_storage_box.cpp
index ca621bda1..476a2d165 100644
--- a/Telegram/SourceFiles/boxes/local_storage_box.cpp
+++ b/Telegram/SourceFiles/boxes/local_storage_box.cpp
@@ -37,29 +37,14 @@ void LocalStorageBox::prepare() {
 }
 
 void LocalStorageBox::updateControls() {
-	auto rowsHeight = 0;
-	if (_imagesCount > 0 && _audiosCount > 0) {
-		rowsHeight = 2 * (st::linkFont->height + st::localStorageBoxSkip);
-	} else {
-		rowsHeight = st::linkFont->height + st::localStorageBoxSkip;
-	}
-	_clear->setVisible(_imagesCount > 0 || _audiosCount > 0);
+	const auto rowsHeight = st::linkFont->height + st::localStorageBoxSkip;
+	_clear->setVisible(false);
 	setDimensions(st::boxWidth, st::localStorageBoxSkip + rowsHeight + _clear->height());
 	_clear->moveToLeft(st::boxPadding.left(), st::localStorageBoxSkip + rowsHeight);
 	update();
 }
 
 void LocalStorageBox::checkLocalStoredCounts() {
-	int imagesCount = Local::hasImages() + Local::hasStickers() + Local::hasWebFiles();
-	int audiosCount = Local::hasAudios();
-	if (imagesCount != _imagesCount || audiosCount != _audiosCount) {
-		_imagesCount = imagesCount;
-		_audiosCount = audiosCount;
-		if (_imagesCount > 0 || _audiosCount > 0) {
-			_state = State::Normal;
-		}
-		updateControls();
-	}
 }
 
 void LocalStorageBox::paintEvent(QPaintEvent *e) {
@@ -71,19 +56,8 @@ void LocalStorageBox::paintEvent(QPaintEvent *e) {
 	p.setPen(st::windowFg);
 	checkLocalStoredCounts();
 	auto top = st::localStorageBoxSkip;
-	if (_imagesCount > 0) {
-		auto text = lng_settings_images_cached(lt_count, _imagesCount, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize() + Local::storageWebFilesSize()));
-		p.drawTextLeft(st::boxPadding.left(), top, width(), text);
-		top += st::boxTextFont->height + st::localStorageBoxSkip;
-	}
-	if (_audiosCount > 0) {
-		auto text = lng_settings_audios_cached(lt_count, _audiosCount, lt_size, formatSizeText(Local::storageAudiosSize()));
-		p.drawTextLeft(st::boxPadding.left(), top, width(), text);
-		top += st::boxTextFont->height + st::localStorageBoxSkip;
-	} else if (_imagesCount <= 0) {
-		p.drawTextLeft(st::boxPadding.left(), top, width(), lang(lng_settings_no_data_cached));
-		top += st::boxTextFont->height + st::localStorageBoxSkip;
-	}
+	p.drawTextLeft(st::boxPadding.left(), top, width(), lang(lng_settings_no_data_cached));
+	top += st::boxTextFont->height + st::localStorageBoxSkip;
 	auto text = ([this]() -> QString {
 		switch (_state) {
 		case State::Clearing: return lang(lng_local_storage_clearing);
diff --git a/Telegram/SourceFiles/boxes/local_storage_box.h b/Telegram/SourceFiles/boxes/local_storage_box.h
index ca6679f14..0bd6c3b29 100644
--- a/Telegram/SourceFiles/boxes/local_storage_box.h
+++ b/Telegram/SourceFiles/boxes/local_storage_box.h
@@ -43,7 +43,4 @@ private:
 
 	object_ptr<Ui::LinkButton> _clear;
 
-	int _imagesCount = -1;
-	int _audiosCount = -1;
-
 };
diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp
index 794061f8a..d4bdadc17 100644
--- a/Telegram/SourceFiles/core/launcher.cpp
+++ b/Telegram/SourceFiles/core/launcher.cpp
@@ -221,7 +221,8 @@ void Launcher::processArguments() {
 	gTestMode = parseResult.contains("-testmode");
 	Logs::SetDebugEnabled(parseResult.contains("-debug"));
 	gManyInstance = parseResult.contains("-many");
-	gKeyFile = parseResult.value("-key", QStringList()).join(QString());
+	gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();
+	gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {});
 	gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
 		: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
 		: parseResult.contains("-cleanup") ? LaunchModeCleanup
@@ -229,8 +230,8 @@ void Launcher::processArguments() {
 	gNoStartUpdate = parseResult.contains("-noupdate");
 	gStartToSettings = parseResult.contains("-tosettings");
 	gStartInTray = parseResult.contains("-startintray");
-	gSendPaths = parseResult.value("-sendpath", QStringList());
-	gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
+	gSendPaths = parseResult.value("-sendpath", {});
+	gWorkingDir = parseResult.value("-workdir", {}).join(QString());
 	if (!gWorkingDir.isEmpty()) {
 		if (QDir().exists(gWorkingDir)) {
 			_customWorkingDir = true;
@@ -238,7 +239,7 @@ void Launcher::processArguments() {
 			gWorkingDir = QString();
 		}
 	}
-	gStartUrl = parseResult.value("--", QStringList()).join(QString());
+	gStartUrl = parseResult.value("--", {}).join(QString());
 }
 
 int Launcher::executeApplication() {
diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp
index 8f678cfa3..ab59eaeec 100644
--- a/Telegram/SourceFiles/data/data_document.cpp
+++ b/Telegram/SourceFiles/data/data_document.cpp
@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item.h"
 #include "history/history_media_types.h"
 #include "window/window_controller.h"
+#include "storage/cache/storage_cache_database.h"
 #include "auth_session.h"
 #include "mainwindow.h"
 #include "messenger.h"
@@ -1087,6 +1088,16 @@ MediaKey DocumentData::mediaKey() const {
 	return ::mediaKey(locationType(), _dc, id);
 }
 
+Storage::Cache::Key DocumentData::cacheKey() const {
+	if (hasWebLocation()) {
+		return Data::WebDocumentCacheKey(_urlLocation);
+	} else if (!_access && !_url.isEmpty()) {
+		return Data::UrlCacheKey(_url);
+	} else {
+		return Data::DocumentCacheKey(_dc, id);
+	}
+}
+
 QString DocumentData::composeNameString() const {
 	if (auto songData = song()) {
 		return ComposeNameString(
@@ -1207,17 +1218,9 @@ void DocumentData::setWebLocation(const WebFileLocation &location) {
 void DocumentData::collectLocalData(DocumentData *local) {
 	if (local == this) return;
 
+	_session->data().cache().copyIfEmpty(local->cacheKey(), cacheKey());
 	if (!local->_data.isEmpty()) {
 		_data = local->_data;
-		if (isVoiceMessage()) {
-			if (!Local::copyAudio(local->mediaKey(), mediaKey())) {
-				Local::writeAudio(mediaKey(), _data);
-			}
-		} else {
-			if (!Local::copyStickerImage(local->mediaKey(), mediaKey())) {
-				Local::writeStickerImage(mediaKey(), _data);
-			}
-		}
 	}
 	if (!local->_location.isEmpty()) {
 		_location = local->_location;
diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h
index 349507859..1286f38c1 100644
--- a/Telegram/SourceFiles/data/data_document.h
+++ b/Telegram/SourceFiles/data/data_document.h
@@ -9,6 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "data/data_types.h"
 
+namespace Storage {
+namespace Cache {
+struct Key;
+} // namespace Cache
+} // namespace Storage
+
 class AuthSession;
 class mtpFileLoader;
 
@@ -173,6 +179,7 @@ public:
 	void setMimeString(const QString &mime);
 
 	MediaKey mediaKey() const;
+	Storage::Cache::Key cacheKey() const;
 
 	static QString ComposeNameString(
 		const QString &filename,
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 14296ee8a..43a62fdbe 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -312,22 +312,20 @@ bool MediaPhoto::updateInlineResultMedia(const MTPMessageMedia &media) {
 	if (media.type() != mtpc_messageMediaPhoto) {
 		return false;
 	}
-	auto &photo = media.c_messageMediaPhoto();
-	if (photo.has_photo() && !photo.has_ttl_seconds()) {
-		if (auto existing = Auth().data().photo(photo.vphoto)) {
-			if (existing == _photo) {
-				return true;
-			} else {
-				// collect data
-			}
+	auto &data = media.c_messageMediaPhoto();
+	if (data.has_photo() && !data.has_ttl_seconds()) {
+		const auto photo = Auth().data().photo(data.vphoto);
+		if (photo == _photo) {
+			return true;
+		} else {
+			photo->collectLocalData(_photo);
 		}
 	} else {
 		LOG(("API Error: "
 			"Got MTPMessageMediaPhoto without photo "
 			"or with ttl_seconds in updateInlineResultMedia()"));
 	}
-	// Can return false if we collect the data.
-	return true;
+	return false;
 }
 
 bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) {
@@ -347,6 +345,20 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) {
 	if (photo.type() != mtpc_photo) {
 		return false;
 	}
+	const auto saveImageToCache = [](
+			const MTPDfileLocation &location,
+			const ImagePtr &image) {
+		const auto key = StorageImageLocation(0, 0, location);
+		if (key.isNull() || image->isNull() || !image->loaded()) {
+			return;
+		}
+		if (image->savedData().isEmpty()) {
+			image->forget();
+		}
+		Auth().data().cache().putIfEmpty(
+			Data::StorageCacheKey(key),
+			image->savedData());
+	};
 	auto &sizes = photo.c_photo().vsizes.v;
 	auto max = 0;
 	const MTPDfileLocation *maxLocation = 0;
@@ -369,23 +381,24 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) {
 		if (!loc || loc->type() != mtpc_fileLocation) {
 			continue;
 		}
+		const auto &location = loc->c_fileLocation();
 		if (size == 's') {
-			Local::writeImage(storageKey(loc->c_fileLocation()), _photo->thumb);
+			saveImageToCache(location, _photo->thumb);
 		} else if (size == 'm') {
-			Local::writeImage(storageKey(loc->c_fileLocation()), _photo->medium);
+			saveImageToCache(location, _photo->medium);
 		} else if (size == 'x' && max < 1) {
 			max = 1;
-			maxLocation = &loc->c_fileLocation();
+			maxLocation = &location;
 		} else if (size == 'y' && max < 2) {
 			max = 2;
-			maxLocation = &loc->c_fileLocation();
+			maxLocation = &location;
 		//} else if (size == 'w' && max < 3) {
 		//	max = 3;
 		//	maxLocation = &loc->c_fileLocation();
 		}
 	}
 	if (maxLocation) {
-		Local::writeImage(storageKey(*maxLocation), _photo->full);
+		saveImageToCache(*maxLocation, _photo->full);
 	}
 	return true;
 }
@@ -637,13 +650,6 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
 		return false;
 	}
 	Auth().data().documentConvert(_document, data.vdocument);
-	if (!_document->data().isEmpty()) {
-		if (_document->isVoiceMessage()) {
-			Local::writeAudio(_document->mediaKey(), _document->data());
-		} else {
-			Local::writeStickerImage(_document->mediaKey(), _document->data());
-		}
-	}
 	return true;
 }
 
diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp
index b0da46ed0..daae1656b 100644
--- a/Telegram/SourceFiles/data/data_photo.cpp
+++ b/Telegram/SourceFiles/data/data_photo.cpp
@@ -111,13 +111,29 @@ void PhotoData::forget() {
 
 ImagePtr PhotoData::makeReplyPreview(Data::FileOrigin origin) {
 	if (replyPreview->isNull() && !thumb->isNull()) {
-		if (thumb->loaded()) {
-			int w = thumb->width(), h = thumb->height();
+		const auto previewFromImage = [&](const ImagePtr &image) {
+			if (!image->loaded()) {
+				image->load(origin);
+				return ImagePtr();
+			}
+			int w = image->width(), h = image->height();
 			if (w <= 0) w = 1;
 			if (h <= 0) h = 1;
-			replyPreview = ImagePtr(w > h ? thumb->pix(origin, w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(origin, st::msgReplyBarSize.height()), "PNG");
+			return ImagePtr(
+				(w > h
+					? image->pix(
+						origin,
+						w * st::msgReplyBarSize.height() / h,
+						st::msgReplyBarSize.height())
+					: image->pix(origin, st::msgReplyBarSize.height())),
+				"PNG");
+		};
+		if (thumb->toDelayedStorageImage()
+			&& !full->isNull()
+			&& !full->toDelayedStorageImage()) {
+			return previewFromImage(full);
 		} else {
-			thumb->load(origin);
+			return previewFromImage(thumb);
 		}
 	}
 	return replyPreview;
@@ -130,6 +146,21 @@ MTPInputPhoto PhotoData::mtpInput() const {
 		MTP_bytes(fileReference));
 }
 
+void PhotoData::collectLocalData(PhotoData *local) {
+	if (local == this) return;
+
+	const auto copyImage = [](const ImagePtr &src, const ImagePtr &dst) {
+		if (const auto from = src->cacheKey()) {
+			if (const auto to = dst->cacheKey()) {
+				Auth().data().cache().copyIfEmpty(*from, *to);
+			}
+		}
+	};
+	copyImage(local->thumb, thumb);
+	copyImage(local->medium, medium);
+	copyImage(local->full, full);
+}
+
 void PhotoOpenClickHandler::onClickImpl() const {
 	Messenger::Instance().showPhoto(this);
 }
diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h
index 79768413d..64dcafd19 100644
--- a/Telegram/SourceFiles/data/data_photo.h
+++ b/Telegram/SourceFiles/data/data_photo.h
@@ -43,6 +43,12 @@ public:
 
 	MTPInputPhoto mtpInput() const;
 
+	// When we have some client-side generated photo
+	// (for example for displaying an external inline bot result)
+	// and it has downloaded full image, we can collect image from it
+	// to (this) received from the server "same" photo.
+	void collectLocalData(PhotoData *local);
+
 	PhotoId id = 0;
 	uint64 access = 0;
 	QByteArray fileReference;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 8a2d6ce3f..d3ad605b0 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_element.h"
 #include "inline_bots/inline_bot_layout_item.h"
 #include "storage/localstorage.h"
+#include "storage/storage_encrypted_file.h"
 #include "boxes/abstract_box.h"
 #include "passport/passport_form_controller.h"
 #include "data/data_media_types.h"
@@ -66,12 +67,19 @@ void UpdateImage(ImagePtr &old, ImagePtr now) {
 
 Session::Session(not_null<AuthSession*> session)
 : _session(session)
+, _cache(Local::cachePath(), Local::cacheSettings())
 , _groups(this)
 , _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
+	_cache.open(Local::cacheKey());
+
 	setupContactViewsViewer();
 	setupChannelLeavingViewer();
 }
 
+Storage::Cache::Database &Session::cache() {
+	return _cache;
+}
+
 void Session::startExport(PeerData *peer) {
 	startExport(peer ? peer->input : MTP_inputPeerEmpty());
 }
@@ -1061,6 +1069,7 @@ void Session::documentConvert(
 		Unexpected("Type in Session::documentConvert().");
 	}();
 	const auto oldKey = original->mediaKey();
+	const auto oldCacheKey = original->cacheKey();
 	const auto idChanged = (original->id != id);
 	const auto sentSticker = idChanged && (original->sticker() != nullptr);
 	if (idChanged) {
@@ -1083,14 +1092,7 @@ void Session::documentConvert(
 	}
 	documentApplyFields(original, data);
 	if (idChanged) {
-		const auto newKey = original->mediaKey();
-		if (oldKey != newKey) {
-			if (original->isVoiceMessage()) {
-				Local::copyAudio(oldKey, newKey);
-			} else if (original->sticker() || original->isAnimation()) {
-				Local::copyStickerImage(oldKey, newKey);
-			}
-		}
+		cache().moveIfEmpty(oldCacheKey, original->cacheKey());
 		if (savedGifs().indexOf(original) >= 0) {
 			Local::writeSavedGifs();
 		}
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 1f33ef83e..4c1991d56 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "storage/cache/storage_cache_database.h"
 #include "chat_helpers/stickers.h"
 #include "dialogs/dialogs_key.h"
 #include "data/data_groups.h"
@@ -71,6 +72,8 @@ public:
 		TimeMs rememberFor);
 	void forgetPassportCredentials();
 
+	Storage::Cache::Database &cache();
+
 	[[nodiscard]] base::Variable<bool> &contactsLoaded() {
 		return _contactsLoaded;
 	}
@@ -520,6 +523,8 @@ private:
 
 	not_null<AuthSession*> _session;
 
+	Storage::Cache::Database _cache;
+
 	std::unique_ptr<Export::ControllerWrap> _export;
 	std::unique_ptr<Export::View::PanelController> _exportPanel;
 	rpl::event_stream<Export::View::PanelController*> _exportViewChanges;
diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp
index 3e71bc1cc..204d99cfb 100644
--- a/Telegram/SourceFiles/data/data_types.cpp
+++ b/Telegram/SourceFiles/data/data_types.cpp
@@ -9,6 +9,72 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "data/data_document.h"
 #include "ui/widgets/input_fields.h"
+#include "storage/cache/storage_cache_types.h"
+#include "base/openssl_help.h"
+
+namespace Data {
+namespace {
+
+constexpr auto kDocumentCacheTag = 0x0000000000000100ULL;
+constexpr auto kDocumentCacheMask = 0x00000000000000FFULL;
+constexpr auto kStorageCacheTag = 0x0000010000000000ULL;
+constexpr auto kStorageCacheMask = 0x000000FFFFFFFFFFULL;
+constexpr auto kWebDocumentCacheTag = 0x0000020000000000ULL;
+constexpr auto kWebDocumentCacheMask = 0x000000FFFFFFFFFFULL;
+constexpr auto kUrlCacheTag = 0x0000030000000000ULL;
+constexpr auto kUrlCacheMask = 0x000000FFFFFFFFFFULL;
+
+} // namespace
+
+Storage::Cache::Key DocumentCacheKey(int32 dcId, uint64 id) {
+	return Storage::Cache::Key{
+		Data::kDocumentCacheTag | (uint64(dcId) & Data::kDocumentCacheMask),
+		id
+	};
+}
+
+Storage::Cache::Key StorageCacheKey(const StorageImageLocation &location) {
+	const auto dcId = uint64(location.dc()) & 0xFFULL;
+	return Storage::Cache::Key{
+		Data::kStorageCacheTag | (dcId << 32) | uint32(location.local()),
+		location.volume()
+	};
+}
+
+Storage::Cache::Key WebDocumentCacheKey(const WebFileLocation &location) {
+	const auto dcId = uint64(location.dc()) & 0xFFULL;
+	const auto url = location.url();
+	const auto hash = openssl::Sha256(bytes::make_span(url));
+	const auto bytes = bytes::make_span(hash);
+	const auto bytes1 = bytes.subspan(0, sizeof(uint32));
+	const auto bytes2 = bytes.subspan(sizeof(uint32), sizeof(uint64));
+	const auto part1 = *reinterpret_cast<const uint32*>(bytes1.data());
+	const auto part2 = *reinterpret_cast<const uint64*>(bytes2.data());
+	return Storage::Cache::Key{
+		Data::kWebDocumentCacheTag | (dcId << 32) | part1,
+		part2
+	};
+}
+
+Storage::Cache::Key UrlCacheKey(const QString &location) {
+	const auto url = location.toUtf8();
+	const auto hash = openssl::Sha256(bytes::make_span(url));
+	const auto bytes = bytes::make_span(hash);
+	const auto bytes1 = bytes.subspan(0, sizeof(uint32));
+	const auto bytes2 = bytes.subspan(sizeof(uint32), sizeof(uint64));
+	const auto bytes3 = bytes.subspan(
+		sizeof(uint32) + sizeof(uint64),
+		sizeof(uint16));
+	const auto part1 = *reinterpret_cast<const uint32*>(bytes1.data());
+	const auto part2 = *reinterpret_cast<const uint64*>(bytes2.data());
+	const auto part3 = *reinterpret_cast<const uint16*>(bytes3.data());
+	return Storage::Cache::Key{
+		Data::kUrlCacheTag | (uint64(part3) << 32) | part1,
+		part2
+	};
+}
+
+} // namespace Data
 
 void AudioMsgId::setTypeFromAudio() {
 	if (_audio->isVoiceMessage() || _audio->isVideoMessage()) {
diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h
index 4e5f4769d..f4f4b5493 100644
--- a/Telegram/SourceFiles/data/data_types.h
+++ b/Telegram/SourceFiles/data/data_types.h
@@ -10,6 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/value_ordering.h"
 #include "ui/text/text.h" // For QFIXED_MAX
 
+namespace Storage {
+namespace Cache {
+struct Key;
+} // namespace Cache
+} // namespace Storage
+
 class HistoryItem;
 using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
 
@@ -17,6 +23,9 @@ namespace Ui {
 class InputField;
 } // namespace Ui
 
+class StorageImageLocation;
+class WebFileLocation;
+
 namespace Data {
 
 struct UploadState {
@@ -27,6 +36,11 @@ struct UploadState {
 	bool waitingForAlbum = false;
 };
 
+Storage::Cache::Key DocumentCacheKey(int32 dcId, uint64 id);
+Storage::Cache::Key StorageCacheKey(const StorageImageLocation &location);
+Storage::Cache::Key WebDocumentCacheKey(const WebFileLocation &location);
+Storage::Cache::Key UrlCacheKey(const QString &location);
+
 } // namespace Data
 
 struct MessageGroupId {
diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp
index 87394c3e4..f7cec2a12 100644
--- a/Telegram/SourceFiles/history/history_media_types.cpp
+++ b/Telegram/SourceFiles/history/history_media_types.cpp
@@ -4320,7 +4320,14 @@ HistoryInvoice::HistoryInvoice(
 }
 
 void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
-	// init attach
+	if (invoice->photo) {
+		_attach = std::make_unique<HistoryPhoto>(
+			_parent,
+			_parent->data(),
+			invoice->photo);
+	} else {
+		_attach = nullptr;
+	}
 	auto labelText = [&] {
 		if (invoice->receiptMsgId) {
 			if (invoice->isTest) {
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index 1ea33da01..8c1fcbe92 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -794,7 +794,7 @@ MainWindow::TempDirState MainWindow::localStorageState() {
 	if (_clearManager && _clearManager->hasTask(Local::ClearManagerStorage)) {
 		return TempDirRemoving;
 	}
-	return (Local::hasImages() || Local::hasStickers() || Local::hasWebFiles() || Local::hasAudios()) ? TempDirExists : TempDirEmpty;
+	return TempDirEmpty;
 }
 
 void MainWindow::tempDirDelete(int task) {
diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp
index 0da5a7ca0..d90cf6e64 100644
--- a/Telegram/SourceFiles/messenger.cpp
+++ b/Telegram/SourceFiles/messenger.cpp
@@ -1226,6 +1226,10 @@ void Messenger::loggedOut() {
 		w->setupIntro();
 	}
 	App::histories().clear();
+	if (const auto session = authSession()) {
+		session->data().cache().close();
+		session->data().cache().clear();
+	}
 	authSessionDestroy();
 	if (_mediaView) {
 		hideMediaView();
diff --git a/Telegram/SourceFiles/mtproto/auth_key.h b/Telegram/SourceFiles/mtproto/auth_key.h
index dcc84291b..cf8f7169e 100644
--- a/Telegram/SourceFiles/mtproto/auth_key.h
+++ b/Telegram/SourceFiles/mtproto/auth_key.h
@@ -56,6 +56,9 @@ public:
 	void write(QDataStream &to) const {
 		to.writeRawData(reinterpret_cast<const char*>(_key.data()), _key.size());
 	}
+	bytes::const_span data() const {
+		return _key;
+	}
 
 	bool equals(const std::shared_ptr<AuthKey> &other) const {
 		return other ? (_key == other->_key) : false;
diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp
index 0f4f06ce4..795f3309f 100644
--- a/Telegram/SourceFiles/overview/overview_layout.cpp
+++ b/Telegram/SourceFiles/overview/overview_layout.cpp
@@ -863,7 +863,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
 	bool selected = (selection == FullSelection);
 
 	_data->automaticLoad(parent()->fullId(), parent());
-	bool loaded = _data->loaded() || Local::willStickerImageLoad(_data->mediaKey()), displayLoading = _data->displayLoading();
+	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
 
 	if (displayLoading) {
 		ensureRadial();
@@ -1024,8 +1024,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
 TextState Document::getState(
 		QPoint point,
 		StateRequest request) const {
-	const auto loaded = _data->loaded()
-		|| Local::willStickerImageLoad(_data->mediaKey());
+	const auto loaded = _data->loaded();
 	const auto wthumb = withThumb();
 
 	if (_data->isAudioFile()) {
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index d1e2c27c9..91937d2d4 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -2338,16 +2338,12 @@ void FormController::fillDownloadedFile(
 	if (!i->uploadData) {
 		return;
 	}
-	Local::writeImage(
-		StorageKey(
-			storageMix32To64(
-				SecureFileLocation,
-				destination.dcId),
-			destination.id),
-		StorageImageSaved(QByteArray::fromRawData(
-			reinterpret_cast<const char*>(
-				i->uploadData->bytes.data()),
-			i->uploadData->bytes.size())));
+	const auto &bytes = i->uploadData->bytes;
+	Auth().data().cache().put(
+		Data::DocumentCacheKey(destination.dcId, destination.id),
+		QByteArray(
+			reinterpret_cast<const char*>(bytes.data()),
+			bytes.size()));
 }
 
 auto FormController::parseValue(
diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_database.cpp b/Telegram/SourceFiles/storage/cache/storage_cache_database.cpp
index c0f33e012..33324da10 100644
--- a/Telegram/SourceFiles/storage/cache/storage_cache_database.cpp
+++ b/Telegram/SourceFiles/storage/cache/storage_cache_database.cpp
@@ -64,20 +64,20 @@ void Database::remove(const Key &key, FnMut<void(Error)> done) {
 	});
 }
 
-void Database::copy(
-		const Key &from,
-		const Key &to,
+void Database::putIfEmpty(
+		const Key &key,
+		QByteArray value,
 		FnMut<void(Error)> done) {
 	_wrapped.with([
-		from,
-		to,
+		key,
+		value = std::move(value),
 		done = std::move(done)
 	](Implementation &unwrapped) mutable {
-		unwrapped.copy(from, to, std::move(done));
+		unwrapped.putIfEmpty(key, std::move(value), std::move(done));
 	});
 }
 
-void Database::move(
+void Database::copyIfEmpty(
 		const Key &from,
 		const Key &to,
 		FnMut<void(Error)> done) {
@@ -86,7 +86,20 @@ void Database::move(
 		to,
 		done = std::move(done)
 	](Implementation &unwrapped) mutable {
-		unwrapped.move(from, to, std::move(done));
+		unwrapped.copyIfEmpty(from, to, std::move(done));
+	});
+}
+
+void Database::moveIfEmpty(
+		const Key &from,
+		const Key &to,
+		FnMut<void(Error)> done) {
+	_wrapped.with([
+		from,
+		to,
+		done = std::move(done)
+	](Implementation &unwrapped) mutable {
+		unwrapped.moveIfEmpty(from, to, std::move(done));
 	});
 }
 
diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_database.h b/Telegram/SourceFiles/storage/cache/storage_cache_database.h
index eb4bd4d44..62a5258cd 100644
--- a/Telegram/SourceFiles/storage/cache/storage_cache_database.h
+++ b/Telegram/SourceFiles/storage/cache/storage_cache_database.h
@@ -35,11 +35,15 @@ public:
 	void get(const Key &key, FnMut<void(QByteArray)> done);
 	void remove(const Key &key, FnMut<void(Error)> done = nullptr);
 
-	void copy(
+	void putIfEmpty(
+		const Key &key,
+		QByteArray value,
+		FnMut<void(Error)> done = nullptr);
+	void copyIfEmpty(
 		const Key &from,
 		const Key &to,
 		FnMut<void(Error)> done = nullptr);
-	void move(
+	void moveIfEmpty(
 		const Key &from,
 		const Key &to,
 		FnMut<void(Error)> done = nullptr);
diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_database_object.cpp b/Telegram/SourceFiles/storage/cache/storage_cache_database_object.cpp
index 830cb0dbd..1a218f298 100644
--- a/Telegram/SourceFiles/storage/cache/storage_cache_database_object.cpp
+++ b/Telegram/SourceFiles/storage/cache/storage_cache_database_object.cpp
@@ -622,9 +622,7 @@ void DatabaseObject::put(
 		QByteArray value,
 		FnMut<void(Error)> done) {
 	if (value.isEmpty()) {
-		remove(key, [done = std::move(done)](Error error) mutable {
-			done(error);
-		});
+		remove(key, std::move(done));
 		return;
 	}
 	_removing.erase(key);
@@ -645,10 +643,12 @@ void DatabaseObject::put(
 	const auto result = data.open(path, File::Mode::Write, _key);
 	switch (result) {
 	case File::Result::Failed:
+		remove(key, nullptr);
 		invokeCallback(done, ioError(path));
 		break;
 
 	case File::Result::LockFailed:
+		remove(key, nullptr);
 		invokeCallback(done, Error{ Error::Type::LockFailed, path });
 		break;
 
@@ -800,8 +800,10 @@ void DatabaseObject::get(const Key &key, FnMut<void(QByteArray)> done) {
 
 	auto result = readValueData(entry.place, entry.size);
 	if (result.isEmpty()) {
+		remove(key, nullptr);
 		invokeCallback(done, QByteArray());
 	} else if (CountChecksum(bytes::make_span(result)) != entry.checksum) {
+		remove(key, nullptr);
 		invokeCallback(done, QByteArray());
 	} else {
 		invokeCallback(done, std::move(result));
@@ -856,22 +858,41 @@ void DatabaseObject::remove(const Key &key, FnMut<void(Error)> done) {
 	}
 }
 
-void DatabaseObject::copy(
+void DatabaseObject::putIfEmpty(
+		const Key &key,
+		QByteArray value,
+		FnMut<void(Error)> done) {
+	if (_map.find(key) != end(_map)) {
+		invokeCallback(done, Error::NoError());
+		return;
+	}
+	put(key, std::move(value), std::move(done));
+}
+
+void DatabaseObject::copyIfEmpty(
 		const Key &from,
 		const Key &to,
 		FnMut<void(Error)> done) {
+	if (_map.find(to) != end(_map)) {
+		invokeCallback(done, Error::NoError());
+		return;
+	}
 	get(from, [&](QByteArray value) {
 		put(to, value, std::move(done));
 	});
 }
 
-void DatabaseObject::move(
+void DatabaseObject::moveIfEmpty(
 		const Key &from,
 		const Key &to,
 		FnMut<void(Error)> done) {
+	if (_map.find(to) != end(_map)) {
+		invokeCallback(done, Error::NoError());
+		return;
+	}
 	const auto i = _map.find(from);
 	if (i == _map.end()) {
-		put(to, QByteArray(), std::move(done));
+		invokeCallback(done, Error::NoError());
 		return;
 	}
 	_removing.emplace(from);
diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_database_object.h b/Telegram/SourceFiles/storage/cache/storage_cache_database_object.h
index 7792f34de..0bfe9dbe2 100644
--- a/Telegram/SourceFiles/storage/cache/storage_cache_database_object.h
+++ b/Telegram/SourceFiles/storage/cache/storage_cache_database_object.h
@@ -37,8 +37,18 @@ public:
 	void get(const Key &key, FnMut<void(QByteArray)> done);
 	void remove(const Key &key, FnMut<void(Error)> done);
 
-	void copy(const Key &from, const Key &to, FnMut<void(Error)> done);
-	void move(const Key &from, const Key &to, FnMut<void(Error)> done);
+	void putIfEmpty(
+		const Key &key,
+		QByteArray value,
+		FnMut<void(Error)> done);
+	void copyIfEmpty(
+		const Key &from,
+		const Key &to,
+		FnMut<void(Error)> done);
+	void moveIfEmpty(
+		const Key &from,
+		const Key &to,
+		FnMut<void(Error)> done);
 
 	void clear(FnMut<void(Error)> done);
 
diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp
index 8378aa7a5..95828fb52 100644
--- a/Telegram/SourceFiles/storage/file_download.cpp
+++ b/Telegram/SourceFiles/storage/file_download.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/file_download.h"
 
 #include "data/data_document.h"
+#include "data/data_session.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "messenger.h"
@@ -36,6 +37,7 @@ void Downloader::clearPriorities() {
 
 void Downloader::requestedAmountIncrement(MTP::DcId dcId, int index, int amount) {
 	Expects(index >= 0 && index < MTP::kDownloadSessionsCount);
+
 	auto it = _requestedBytesAmount.find(dcId);
 	if (it == _requestedBytesAmount.cend()) {
 		it = _requestedBytesAmount.emplace(dcId, RequestedInDc { { 0 } }).first;
@@ -206,16 +208,16 @@ void FileLoader::pause() {
 }
 
 FileLoader::~FileLoader() {
-	if (_localTaskId) {
-		Local::cancelTask(_localTaskId);
-	}
 	removeFromQueue();
 }
 
-void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat, const QPixmap &imagePixmap) {
-	_localTaskId = 0;
+void FileLoader::localLoaded(
+		const StorageImageSaved &result,
+		const QByteArray &imageFormat,
+		const QPixmap &imagePixmap) {
+	_localLoading.kill();
 	if (result.data.isEmpty()) {
-		_localStatus = LocalFailed;
+		_localStatus = LocalStatus::NotFound;
 		start(true);
 		return;
 	}
@@ -224,7 +226,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
 		_imageFormat = imageFormat;
 		_imagePixmap = imagePixmap;
 	}
-	_localStatus = LocalLoaded;
+	_localStatus = LocalStatus::Loaded;
 	if (!_filename.isEmpty() && _toCache == LoadToCacheAsWell) {
 		if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly);
 		if (!_fileIsOpen) {
@@ -241,7 +243,8 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
 	if (_fileIsOpen) {
 		_file.close();
 		_fileIsOpen = false;
-		Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath());
+		Platform::File::PostprocessDownloaded(
+			QFileInfo(_file).absoluteFilePath());
 	}
 	_downloader->taskFinished().notify();
 
@@ -254,9 +257,9 @@ void FileLoader::start(bool loadFirst, bool prior) {
 	if (_paused) {
 		_paused = false;
 	}
-	if (_finished || tryLoadLocal()) return;
-
-	if (_fromCloud == LoadFromLocalOnly) {
+	if (_finished || tryLoadLocal()) {
+		return;
+	} else if (_fromCloud == LoadFromLocalOnly) {
 		cancel();
 		return;
 	}
@@ -357,6 +360,74 @@ void FileLoader::start(bool loadFirst, bool prior) {
 	return startLoading(loadFirst, prior);
 }
 
+void FileLoader::loadLocal(const Storage::Cache::Key &key) {
+	const auto readImage = (_locationType != AudioFileLocation);
+	auto [first, second] = base::make_binary_guard();
+	_localLoading = std::move(first);
+	auto done = [=, guard = std::move(second)](
+			QByteArray value,
+			QImage image,
+			QByteArray format) mutable {
+		crl::on_main([
+			=,
+			value = std::move(value),
+			image = std::move(image),
+			format = std::move(format),
+			guard = std::move(guard)
+		]() mutable {
+			if (!guard.alive()) {
+				return;
+			}
+			localLoaded(
+				StorageImageSaved(std::move(value)),
+				format,
+				App::pixmapFromImageInPlace(std::move(image)));
+		});
+	};
+	Auth().data().cache().get(key, [=, callback = std::move(done)](
+			QByteArray value) mutable {
+		if (readImage) {
+			crl::async([
+				value = std::move(value),
+				done = std::move(callback)
+			]() mutable {
+				auto format = QByteArray();
+				auto image = App::readImage(value, &format, false);
+				if (!image.isNull()) {
+					done(value, image, format);
+				} else {
+					done(value, {}, {});
+				}
+			});
+		} else {
+			callback(value, {}, {});
+		}
+	});
+}
+
+bool FileLoader::tryLoadLocal() {
+	if (_localStatus == LocalStatus::NotFound
+		|| _localStatus == LocalStatus::Loaded) {
+		return false;
+	} else if (_localStatus == LocalStatus::Loading) {
+		return true;
+	}
+
+	if (const auto key = cacheKey()) {
+		loadLocal(*key);
+		emit progress(this);
+	}
+
+	if (_localStatus != LocalStatus::NotTried) {
+		return _finished;
+	} else if (_localLoading.alive()) {
+		_localStatus = LocalStatus::Loading;
+		return true;
+	}
+	_localStatus = LocalStatus::NotFound;
+	return false;
+}
+
 void FileLoader::cancel() {
 	cancel(false);
 }
@@ -872,29 +943,19 @@ bool mtpFileLoader::feedPart(int offset, bytes::const_span buffer) {
 		}
 		removeFromQueue();
 
-		if (_localStatus == LocalNotFound || _localStatus == LocalFailed) {
-			if (_urlLocation) {
-				Local::writeImage(storageKey(*_urlLocation), StorageImageSaved(_data));
-			} else if (_locationType != UnknownFileLocation) { // audio, video, document
-				auto mkey = mediaKey(_locationType, _dcId, _id);
-				if (!_filename.isEmpty()) {
-					Local::writeFileLocation(mkey, FileLocation(_filename));
+		if (_localStatus == LocalStatus::NotFound) {
+			if (_locationType != UnknownFileLocation
+				&& !_filename.isEmpty()) {
+				Local::writeFileLocation(
+					mediaKey(_locationType, _dcId, _id),
+					FileLocation(_filename));
+			}
+			if (_urlLocation
+				|| _locationType == UnknownFileLocation
+				|| _toCache == LoadToCacheAsWell) {
+				if (const auto key = cacheKey()) {
+					Auth().data().cache().put(*key, _data);
 				}
-				if (_toCache == LoadToCacheAsWell) {
-					if (_locationType == DocumentFileLocation) {
-						Local::writeStickerImage(mkey, _data);
-					} else if (_locationType == AudioFileLocation) {
-						Local::writeAudio(mkey, _data);
-					} else if (_locationType == SecureFileLocation) {
-						Local::writeImage(
-							StorageKey(
-								storageMix32To64(_locationType, _dcId),
-								_id),
-							StorageImageSaved(_data));
-					}
-				}
-			} else {
-				Local::writeImage(storageKey(*_location), StorageImageSaved(_data));
 			}
 		}
 	}
@@ -1026,43 +1087,15 @@ void mtpFileLoader::changeCDNParams(
 	makeRequest(offset);
 }
 
-bool mtpFileLoader::tryLoadLocal() {
-	if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) {
-		return false;
-	}
-	if (_localStatus == LocalLoading) {
-		return true;
-	}
-
+base::optional<Storage::Cache::Key> mtpFileLoader::cacheKey() const {
 	if (_urlLocation) {
-		_localTaskId = Local::startImageLoad(storageKey(*_urlLocation), this);
+		return Data::WebDocumentCacheKey(*_urlLocation);
 	} else if (_location) {
-		_localTaskId = Local::startImageLoad(storageKey(*_location), this);
-	} else {
-		if (_toCache == LoadToCacheAsWell) {
-			MediaKey mkey = mediaKey(_locationType, _dcId, _id);
-			if (_locationType == DocumentFileLocation) {
-				_localTaskId = Local::startStickerImageLoad(mkey, this);
-			} else if (_locationType == AudioFileLocation) {
-				_localTaskId = Local::startAudioLoad(mkey, this);
-			} else if (_locationType == SecureFileLocation) {
-				_localTaskId = Local::startImageLoad(StorageKey(
-					storageMix32To64(_locationType, _dcId),
-					_id), this);
-			}
-		}
+		return Data::StorageCacheKey(*_location);
+	} else if (_toCache == LoadToCacheAsWell) {
+		return Data::DocumentCacheKey(_dcId, _id);
 	}
-
-	emit progress(this);
-
-	if (_localStatus != LocalNotTried) {
-		return _finished;
-	} else if (_localTaskId) {
-		_localStatus = LocalLoading;
-		return true;
-	}
-	_localStatus = LocalNotFound;
-	return false;
+	return base::none;
 }
 
 mtpFileLoader::~mtpFileLoader() {
@@ -1129,8 +1162,10 @@ void webFileLoader::onFinished(const QByteArray &data) {
 	}
 	removeFromQueue();
 
-	if (_localStatus == LocalNotFound || _localStatus == LocalFailed) {
-		Local::writeWebFile(_url, _data);
+	if (_localStatus == LocalStatus::NotFound) {
+		if (const auto key = cacheKey()) {
+			Auth().data().cache().put(*key, _data);
+		}
 	}
 	_downloader->taskFinished().notify();
 
@@ -1143,23 +1178,8 @@ void webFileLoader::onError() {
 	cancel(true);
 }
 
-bool webFileLoader::tryLoadLocal() {
-	if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) {
-		return false;
-	}
-	if (_localStatus == LocalLoading) {
-		return true;
-	}
-
-	_localTaskId = Local::startWebFileLoad(_url, this);
-	if (_localStatus != LocalNotTried) {
-		return _finished;
-	} else if (_localTaskId) {
-		_localStatus = LocalLoading;
-		return true;
-	}
-	_localStatus = LocalNotFound;
-	return false;
+base::optional<Storage::Cache::Key> webFileLoader::cacheKey() const {
+	return Data::UrlCacheKey(_url);
 }
 
 void webFileLoader::cancelRequests() {
diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h
index 7ef00e4c5..94ac563d1 100644
--- a/Telegram/SourceFiles/storage/file_download.h
+++ b/Telegram/SourceFiles/storage/file_download.h
@@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "base/observer.h"
-#include "storage/localimageloader.h" // for TaskId
 #include "data/data_file_origin.h"
+#include "base/binary_guard.h"
 
 namespace Storage {
+namespace Cache {
+struct Key;
+} // namespace Cache
 
 constexpr auto kMaxFileInMemory = 10 * 1024 * 1024; // 10 MB max file could be hold in memory
 constexpr auto kMaxVoiceInMemory = 2 * 1024 * 1024; // 2 MB audio is hold in memory and auto loaded
@@ -61,14 +64,6 @@ struct StorageImageSaved {
 
 };
 
-enum LocalLoadStatus {
-	LocalNotTried,
-	LocalNotFound,
-	LocalLoading,
-	LocalLoaded,
-	LocalFailed,
-};
-
 class mtpFileLoader;
 class webFileLoader;
 
@@ -119,7 +114,7 @@ public:
 		return _inQueue || _paused;
 	}
 	bool loadingLocal() const {
-		return (_localStatus == LocalLoading);
+		return (_localStatus == LocalStatus::Loading);
 	}
 	bool autoLoading() const {
 		return _autoLoading;
@@ -129,15 +124,37 @@ public:
 	}
 	virtual ~FileLoader();
 
-	void localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat = QByteArray(), const QPixmap &imagePixmap = QPixmap());
+	void localLoaded(
+		const StorageImageSaved &result,
+		const QByteArray &imageFormat = QByteArray(),
+		const QPixmap &imagePixmap = QPixmap());
 
 signals:
 	void progress(FileLoader *loader);
 	void failed(FileLoader *loader, bool started);
 
 protected:
+	enum class LocalStatus {
+		NotTried,
+		NotFound,
+		Loading,
+		Loaded,
+	};
+
 	void readImage(const QSize &shrinkBox) const;
 
+	bool tryLoadLocal();
+	void loadLocal(const Storage::Cache::Key &key);
+	virtual base::optional<Storage::Cache::Key> cacheKey() const = 0;
+	virtual void cancelRequests() = 0;
+
+	void startLoading(bool loadFirst, bool prior);
+	void removeFromQueue();
+	void cancel(bool failed);
+
+	void loadNext();
+	virtual bool loadPart() = 0;
+
 	not_null<Storage::Downloader*> _downloader;
 	FileLoader *_prev = nullptr;
 	FileLoader *_next = nullptr;
@@ -149,17 +166,7 @@ protected:
 	bool _inQueue = false;
 	bool _finished = false;
 	bool _cancelled = false;
-	mutable LocalLoadStatus _localStatus = LocalNotTried;
-
-	virtual bool tryLoadLocal() = 0;
-	virtual void cancelRequests() = 0;
-
-	void startLoading(bool loadFirst, bool prior);
-	void removeFromQueue();
-	void cancel(bool failed);
-
-	void loadNext();
-	virtual bool loadPart() = 0;
+	mutable LocalStatus _localStatus = LocalStatus::NotTried;
 
 	QString _filename;
 	QFile _file;
@@ -173,7 +180,7 @@ protected:
 	int32 _size;
 	LocationType _locationType;
 
-	TaskId _localTaskId = 0;
+	base::binary_guard _localLoading;
 	mutable QByteArray _imageFormat;
 	mutable QPixmap _imagePixmap;
 
@@ -238,8 +245,7 @@ private:
 		int limit = 0;
 		QByteArray hash;
 	};
-
-	bool tryLoadLocal() override;
+	base::optional<Storage::Cache::Key> cacheKey() const override;
 	void cancelRequests() override;
 
 	int partSize() const;
@@ -307,32 +313,28 @@ class webFileLoader : public FileLoader {
 	Q_OBJECT
 
 public:
+	webFileLoader(
+		const QString &url,
+		const QString &to,
+		LoadFromCloudSetting fromCloud,
+		bool autoLoading);
 
-	webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading);
-
-	virtual int32 currentOffset(bool includeSkipped = false) const;
-	virtual webFileLoader *webLoader() {
-		return this;
-	}
-	virtual const webFileLoader *webLoader() const {
-		return this;
-	}
+	int32 currentOffset(bool includeSkipped = false) const override;
 
 	void onProgress(qint64 already, qint64 size);
 	void onFinished(const QByteArray &data);
 	void onError();
 
-	virtual void stop() {
+	void stop() override {
 		cancelRequests();
 	}
 
 	~webFileLoader();
 
 protected:
-
-	virtual void cancelRequests();
-	virtual bool tryLoadLocal();
-	virtual bool loadPart();
+	void cancelRequests() override;
+	base::optional<Storage::Cache::Key> cacheKey() const override;
+	bool loadPart() override;
 
 	QString _url;
 
diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp
index 4831e062f..c999c8608 100644
--- a/Telegram/SourceFiles/storage/file_upload.cpp
+++ b/Telegram/SourceFiles/storage/file_upload.cpp
@@ -126,6 +126,9 @@ void Uploader::uploadMedia(const FullMsgId &msgId, const SendMediaReady &media)
 			: Auth().data().document(media.document, media.photoThumbs.begin().value());
 		if (!media.data.isEmpty()) {
 			document->setData(media.data);
+			if (document->saveToCache()) {
+				Auth().data().cache().put(document->cacheKey(), media.data);
+			}
 		}
 		if (!media.file.isEmpty()) {
 			document->setLocation(FileLocation(media.file));
@@ -148,6 +151,11 @@ void Uploader::upload(
 		document->uploadingData = std::make_unique<Data::UploadState>(document->size);
 		if (!file->content.isEmpty()) {
 			document->setData(file->content);
+			if (document->saveToCache()) {
+				Auth().data().cache().put(
+					document->cacheKey(),
+					file->content);
+			}
 		}
 		if (!file->filepath.isEmpty()) {
 			document->setLocation(FileLocation(file->filepath));
diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp
index 21ed750c9..a4354e27d 100644
--- a/Telegram/SourceFiles/storage/localstorage.cpp
+++ b/Telegram/SourceFiles/storage/localstorage.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "storage/serialize_document.h"
 #include "storage/serialize_common.h"
+#include "storage/storage_encrypted_file.h"
 #include "chat_helpers/stickers.h"
 #include "data/data_drafts.h"
 #include "boxes/send_files_box.h"
@@ -65,7 +66,7 @@ QString toFilePart(FileKey val) {
 	return result;
 }
 
-QString _basePath, _userBasePath;
+QString _basePath, _userBasePath, _userDbPath;
 
 bool _started = false;
 internal::Manager *_manager = nullptr;
@@ -620,9 +621,6 @@ typedef QMap<QString, FileLocationPair> FileLocationPairs;
 FileLocationPairs _fileLocationPairs;
 typedef QMap<MediaKey, MediaKey> FileLocationAliases;
 FileLocationAliases _fileLocationAliases;
-typedef QMap<QString, FileDesc> WebFilesMap;
-WebFilesMap _webFilesMap;
-uint64 _storageWebFilesSize = 0;
 FileKey _locationsKey = 0, _reportSpamStatusesKey = 0, _trustedBotsKey = 0;
 
 using TrustedBots = OrderedSet<uint64>;
@@ -655,10 +653,6 @@ FileKey _exportSettingsKey = 0;
 FileKey _savedPeersKey = 0;
 FileKey _langPackKey = 0;
 
-typedef QMap<StorageKey, FileDesc> StorageMap;
-StorageMap _imagesMap, _stickerImagesMap, _audiosMap;
-qint64 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0;
-
 bool _mapChanged = false;
 int32 _oldMapVersion = 0, _oldSettingsVersion = 0;
 
@@ -686,7 +680,7 @@ void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
 	if (!_working()) return;
 
 	_manager->writingLocations();
-	if (_fileLocations.isEmpty() && _webFilesMap.isEmpty()) {
+	if (_fileLocations.isEmpty()) {
 		if (_locationsKey) {
 			clearKey(_locationsKey);
 			_locationsKey = 0;
@@ -724,12 +718,6 @@ void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
 			size += sizeof(quint64) * 2 + sizeof(quint64) * 2;
 		}
 
-		size += sizeof(quint32); // web files count
-		for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) {
-			// url + filekey + size
-			size += Serialize::stringSize(i.key()) + sizeof(quint64) + sizeof(qint32);
-		}
-
 		EncryptedDescriptor data(size);
 		auto legacyTypeField = 0;
 		for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) {
@@ -751,11 +739,6 @@ void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
 			data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second);
 		}
 
-		data.stream << quint32(_webFilesMap.size());
-		for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) {
-			data.stream << i.key() << quint64(i.value().first) << qint32(i.value().second);
-		}
-
 		FileWriteDescriptor file(_locationsKey);
 		file.writeEncrypted(data);
 	}
@@ -804,9 +787,6 @@ void _readLocations() {
 		}
 
 		if (!locations.stream.atEnd()) {
-			_storageWebFilesSize = 0;
-			_webFilesMap.clear();
-
 			quint32 webLocationsCount;
 			locations.stream >> webLocationsCount;
 			for (quint32 i = 0; i < webLocationsCount; ++i) {
@@ -814,8 +794,7 @@ void _readLocations() {
 				quint64 key;
 				qint32 size;
 				locations.stream >> url >> key >> size;
-				_webFilesMap.insert(url, FileDesc(key, size));
-				_storageWebFilesSize += size;
+				clearKey(key, FileOption::User);
 			}
 		}
 	}
@@ -2071,6 +2050,10 @@ ReadMapState _readMap(const QByteArray &pass) {
 	hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
 	_dataNameKey = dataNameHash[0];
 	_userBasePath = _basePath + toFilePart(_dataNameKey) + QChar('/');
+	_userDbPath = _basePath
+		+ "user_" + cDataFile()
+		+ (cTestMode() ? "[test]" : "")
+		+ '/';
 
 	FileReadDescriptor mapData;
 	if (!readFile(mapData, qsl("map"))) {
@@ -2113,8 +2096,6 @@ ReadMapState _readMap(const QByteArray &pass) {
 
 	DraftsMap draftsMap, draftCursorsMap;
 	DraftsNotReadMap draftsNotReadMap;
-	StorageMap imagesMap, stickerImagesMap, audiosMap;
-	qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0;
 	quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0;
 	quint64 recentStickersKeyOld = 0;
 	quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0;
@@ -2154,8 +2135,7 @@ ReadMapState _readMap(const QByteArray &pass) {
 				quint64 first, second;
 				qint32 size;
 				map.stream >> key >> first >> second >> size;
-				imagesMap.insert(StorageKey(first, second), FileDesc(key, size));
-				storageImagesSize += size;
+				clearKey(key, FileOption::User);
 			}
 		} break;
 		case lskStickerImages: {
@@ -2166,8 +2146,7 @@ ReadMapState _readMap(const QByteArray &pass) {
 				quint64 first, second;
 				qint32 size;
 				map.stream >> key >> first >> second >> size;
-				stickerImagesMap.insert(StorageKey(first, second), FileDesc(key, size));
-				storageStickersSize += size;
+				clearKey(key, FileOption::User);
 			}
 		} break;
 		case lskAudios: {
@@ -2178,8 +2157,7 @@ ReadMapState _readMap(const QByteArray &pass) {
 				quint64 first, second;
 				qint32 size;
 				map.stream >> key >> first >> second >> size;
-				audiosMap.insert(StorageKey(first, second), FileDesc(key, size));
-				storageAudiosSize += size;
+				clearKey(key, FileOption::User);
 			}
 		} break;
 		case lskLocations: {
@@ -2243,13 +2221,6 @@ ReadMapState _readMap(const QByteArray &pass) {
 	_draftCursorsMap = draftCursorsMap;
 	_draftsNotReadMap = draftsNotReadMap;
 
-	_imagesMap = imagesMap;
-	_storageImagesSize = storageImagesSize;
-	_stickerImagesMap = stickerImagesMap;
-	_storageStickersSize = storageStickersSize;
-	_audiosMap = audiosMap;
-	_storageAudiosSize = storageAudiosSize;
-
 	_locationsKey = locationsKey;
 	_reportSpamStatusesKey = reportSpamStatusesKey;
 	_trustedBotsKey = trustedBotsKey;
@@ -2328,9 +2299,6 @@ void _writeMap(WriteMapWhen when) {
 	uint32 mapSize = 0;
 	if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
 	if (!_draftCursorsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2;
-	if (!_imagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _imagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
-	if (!_stickerImagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _stickerImagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
-	if (!_audiosMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _audiosMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
 	if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 	if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64);
 	if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
@@ -2346,16 +2314,6 @@ void _writeMap(WriteMapWhen when) {
 	if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 	if (_exportSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 
-	if (mapSize > 30 * 1024 * 1024) {
-		CrashReports::SetAnnotation("MapSize", QString("%1,%2,%3,%4,%5"
-		).arg(_draftsMap.size()
-		).arg(_draftCursorsMap.size()
-		).arg(_imagesMap.size()
-		).arg(_stickerImagesMap.size()
-		).arg(_audiosMap.size()
-		));
-	}
-
 	EncryptedDescriptor mapData(mapSize);
 
 	if (!_draftsMap.isEmpty()) {
@@ -2370,24 +2328,6 @@ void _writeMap(WriteMapWhen when) {
 			mapData.stream << quint64(i.value()) << quint64(i.key());
 		}
 	}
-	if (!_imagesMap.isEmpty()) {
-		mapData.stream << quint32(lskImages) << quint32(_imagesMap.size());
-		for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) {
-			mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
-		}
-	}
-	if (!_stickerImagesMap.isEmpty()) {
-		mapData.stream << quint32(lskStickerImages) << quint32(_stickerImagesMap.size());
-		for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) {
-			mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
-		}
-	}
-	if (!_audiosMap.isEmpty()) {
-		mapData.stream << quint32(lskAudios) << quint32(_audiosMap.size());
-		for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) {
-			mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
-		}
-	}
 	if (_locationsKey) {
 		mapData.stream << quint32(lskLocations) << quint64(_locationsKey);
 	}
@@ -2431,10 +2371,6 @@ void _writeMap(WriteMapWhen when) {
 	map.writeEncrypted(mapData);
 
 	_mapChanged = false;
-
-	if (mapSize > 30 * 1024 * 1024) {
-		CrashReports::ClearAnnotation("MapSize");
-	}
 }
 
 } // namespace
@@ -2683,13 +2619,7 @@ void reset() {
 	_fileLocations.clear();
 	_fileLocationPairs.clear();
 	_fileLocationAliases.clear();
-	_imagesMap.clear();
 	_draftsNotReadMap.clear();
-	_stickerImagesMap.clear();
-	_audiosMap.clear();
-	_storageImagesSize = _storageStickersSize = _storageAudiosSize = 0;
-	_webFilesMap.clear();
-	_storageWebFilesSize = 0;
 	_locationsKey = _reportSpamStatusesKey = _trustedBotsKey = 0;
 	_recentStickersKeyOld = 0;
 	_installedStickersKey = _featuredStickersKey = _recentStickersKey = _favedStickersKey = _archivedStickersKey = 0;
@@ -3011,401 +2941,23 @@ qint32 _storageAudioSize(qint32 rawlen) {
 	return result;
 }
 
-void writeImage(const StorageKey &location, const ImagePtr &image) {
-	if (image->isNull() || !image->loaded()) return;
-	if (_imagesMap.constFind(location) != _imagesMap.cend()) return;
+QString cachePath() {
+	Expects(!_userDbPath.isEmpty());
 
-	image->forget();
-	writeImage(location, StorageImageSaved(image->savedData()), false);
+	return _userDbPath + "cache";
 }
 
-void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) {
-	if (!_working()) return;
-
-	qint32 size = _storageImageSize(image.data.size());
-	StorageMap::const_iterator i = _imagesMap.constFind(location);
-	if (i == _imagesMap.cend()) {
-		i = _imagesMap.insert(location, FileDesc(genKey(FileOption::User), size));
-		_storageImagesSize += size;
-		_mapChanged = true;
-		_writeMap();
-	} else if (!overwrite) {
-		return;
-	}
-
-	auto legacyTypeField = 0;
-
-	EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size());
-	data.stream << quint64(location.first) << quint64(location.second) << quint32(legacyTypeField) << image.data;
-
-	FileWriteDescriptor file(i.value().first, FileOption::User);
-	file.writeEncrypted(data);
-	if (i.value().second != size) {
-		_storageImagesSize += size;
-		_storageImagesSize -= i.value().second;
-		_imagesMap[location].second = size;
-	}
-}
-
-class AbstractCachedLoadTask : public Task {
-public:
-
-	AbstractCachedLoadTask(const FileKey &key, const StorageKey &location, bool readImageFlag, mtpFileLoader *loader) :
-		_key(key), _location(location), _readImageFlag(readImageFlag), _loader(loader), _result(0) {
-	}
-	void process() {
-		FileReadDescriptor image;
-		if (!readEncryptedFile(image, _key, FileOption::User)) {
-			return;
-		}
-
-		QByteArray imageData;
-		quint64 locFirst, locSecond;
-		quint32 legacyTypeField = 0;
-		readFromStream(image.stream, locFirst, locSecond, imageData);
-
-		// we're saving files now before we have actual location
-		//if (locFirst != _location.first || locSecond != _location.second) {
-		//	return;
-		//}
-
-		_result = new Result(imageData, _readImageFlag);
-	}
-	void finish() {
-		if (_result) {
-			_loader->localLoaded(_result->image, _result->format, _result->pixmap);
-		} else {
-			clearInMap();
-			_loader->localLoaded(StorageImageSaved());
-		}
-	}
-	virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) = 0;
-	virtual void clearInMap() = 0;
-	virtual ~AbstractCachedLoadTask() {
-		delete base::take(_result);
-	}
-
-protected:
-	FileKey _key;
-	StorageKey _location;
-	bool _readImageFlag;
-	struct Result {
-		Result(const QByteArray &data, bool readImageFlag) : image(data) {
-			if (readImageFlag) {
-				auto realFormat = QByteArray();
-				pixmap = App::pixmapFromImageInPlace(App::readImage(data, &realFormat, false));
-				if (!pixmap.isNull()) {
-					format = realFormat;
-				}
-			}
-		}
-		StorageImageSaved image;
-		QByteArray format;
-		QPixmap pixmap;
-
-	};
-	mtpFileLoader *_loader;
-	Result *_result;
-
-};
-
-class ImageLoadTask : public AbstractCachedLoadTask {
-public:
-	ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
-	AbstractCachedLoadTask(key, location, true, loader) {
-	}
-	void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) override {
-		qint32 legacyTypeField = 0;
-		stream >> first >> second >> legacyTypeField >> data;
-	}
-	void clearInMap() override {
-		StorageMap::iterator j = _imagesMap.find(_location);
-		if (j != _imagesMap.cend() && j->first == _key) {
-			clearKey(_key, FileOption::User);
-			_storageImagesSize -= j->second;
-			_imagesMap.erase(j);
-		}
-	}
-};
-
-TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) {
-	StorageMap::const_iterator j = _imagesMap.constFind(location);
-	if (j == _imagesMap.cend() || !_localLoader) {
-		return 0;
-	}
-	return _localLoader->addTask(
-		std::make_unique<ImageLoadTask>(j->first, location, loader));
-}
-
-bool willImageLoad(const StorageKey &location) {
-	return _imagesMap.constFind(location) != _imagesMap.cend();
-}
-
-int32 hasImages() {
-	return _imagesMap.size();
-}
-
-qint64 storageImagesSize() {
-	return _storageImagesSize;
-}
-
-void writeStickerImage(const StorageKey &location, const QByteArray &sticker, bool overwrite) {
-	if (!_working()) return;
-
-	qint32 size = _storageStickerSize(sticker.size());
-	StorageMap::const_iterator i = _stickerImagesMap.constFind(location);
-	if (i == _stickerImagesMap.cend()) {
-		i = _stickerImagesMap.insert(location, FileDesc(genKey(FileOption::User), size));
-		_storageStickersSize += size;
-		_mapChanged = true;
-		_writeMap();
-	} else if (!overwrite) {
-		return;
-	}
-	EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + sticker.size());
-	data.stream << quint64(location.first) << quint64(location.second) << sticker;
-	FileWriteDescriptor file(i.value().first, FileOption::User);
-	file.writeEncrypted(data);
-	if (i.value().second != size) {
-		_storageStickersSize += size;
-		_storageStickersSize -= i.value().second;
-		_stickerImagesMap[location].second = size;
-	}
-}
-
-class StickerImageLoadTask : public AbstractCachedLoadTask {
-public:
-	StickerImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
-	AbstractCachedLoadTask(key, location, true, loader) {
-	}
-	void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) {
-		stream >> first >> second >> data;
-	}
-	void clearInMap() {
-		auto j = _stickerImagesMap.find(_location);
-		if (j != _stickerImagesMap.cend() && j->first == _key) {
-			clearKey(j.value().first, FileOption::User);
-			_storageStickersSize -= j.value().second;
-			_stickerImagesMap.erase(j);
-		}
-	}
-};
-
-TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) {
-	auto j = _stickerImagesMap.constFind(location);
-	if (j == _stickerImagesMap.cend() || !_localLoader) {
-		return 0;
-	}
-	return _localLoader->addTask(
-		std::make_unique<StickerImageLoadTask>(j->first, location, loader));
-}
-
-bool willStickerImageLoad(const StorageKey &location) {
-	return _stickerImagesMap.constFind(location) != _stickerImagesMap.cend();
-}
-
-bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation) {
-	auto i = _stickerImagesMap.constFind(oldLocation);
-	if (i == _stickerImagesMap.cend()) {
-		return false;
-	}
-	_stickerImagesMap.insert(newLocation, i.value());
-	_mapChanged = true;
-	_writeMap();
-	return true;
-}
-
-int32 hasStickers() {
-	return _stickerImagesMap.size();
-}
-
-qint64 storageStickersSize() {
-	return _storageStickersSize;
-}
-
-void writeAudio(const StorageKey &location, const QByteArray &audio, bool overwrite) {
-	if (!_working()) return;
-
-	qint32 size = _storageAudioSize(audio.size());
-	StorageMap::const_iterator i = _audiosMap.constFind(location);
-	if (i == _audiosMap.cend()) {
-		i = _audiosMap.insert(location, FileDesc(genKey(FileOption::User), size));
-		_storageAudiosSize += size;
-		_mapChanged = true;
-		_writeMap();
-	} else if (!overwrite) {
-		return;
-	}
-	EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + audio.size());
-	data.stream << quint64(location.first) << quint64(location.second) << audio;
-	FileWriteDescriptor file(i.value().first, FileOption::User);
-	file.writeEncrypted(data);
-	if (i.value().second != size) {
-		_storageAudiosSize += size;
-		_storageAudiosSize -= i.value().second;
-		_audiosMap[location].second = size;
-	}
-}
-
-class AudioLoadTask : public AbstractCachedLoadTask {
-public:
-	AudioLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) :
-	AbstractCachedLoadTask(key, location, false, loader) {
-	}
-	void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, QByteArray &data) {
-		stream >> first >> second >> data;
-	}
-	void clearInMap() {
-		auto j = _audiosMap.find(_location);
-		if (j != _audiosMap.cend() && j->first == _key) {
-			clearKey(j.value().first, FileOption::User);
-			_storageAudiosSize -= j.value().second;
-			_audiosMap.erase(j);
-		}
-	}
-};
-
-TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) {
-	auto j = _audiosMap.constFind(location);
-	if (j == _audiosMap.cend() || !_localLoader) {
-		return 0;
-	}
-	return _localLoader->addTask(
-		std::make_unique<AudioLoadTask>(j->first, location, loader));
-}
-
-bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation) {
-	auto i = _audiosMap.constFind(oldLocation);
-	if (i == _audiosMap.cend()) {
-		return false;
-	}
-	_audiosMap.insert(newLocation, i.value());
-	_mapChanged = true;
-	_writeMap();
-	return true;
-}
-
-bool willAudioLoad(const StorageKey &location) {
-	return _audiosMap.constFind(location) != _audiosMap.cend();
-}
-
-int32 hasAudios() {
-	return _audiosMap.size();
-}
-
-qint64 storageAudiosSize() {
-	return _storageAudiosSize;
-}
-
-qint32 _storageWebFileSize(const QString &url, qint32 rawlen) {
-	// fulllen + url + len + data
-	qint32 result = sizeof(uint32) + Serialize::stringSize(url) + 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
+Storage::Cache::Database::Settings cacheSettings() {
+	auto result = Storage::Cache::Database::Settings();
 	return result;
 }
 
-void writeWebFile(const QString &url, const QByteArray &content, bool overwrite) {
-	if (!_working()) return;
+Storage::EncryptionKey cacheKey() {
+	Expects(LocalKey != nullptr);
 
-	qint32 size = _storageWebFileSize(url, content.size());
-	WebFilesMap::const_iterator i = _webFilesMap.constFind(url);
-	if (i == _webFilesMap.cend()) {
-		i = _webFilesMap.insert(url, FileDesc(genKey(FileOption::User), size));
-		_storageWebFilesSize += size;
-		_writeLocations();
-	} else if (!overwrite) {
-		return;
-	}
-	EncryptedDescriptor data(Serialize::stringSize(url) + sizeof(quint32) + sizeof(quint32) + content.size());
-	data.stream << url << content;
-	FileWriteDescriptor file(i.value().first, FileOption::User);
-	file.writeEncrypted(data);
-	if (i.value().second != size) {
-		_storageWebFilesSize += size;
-		_storageWebFilesSize -= i.value().second;
-		_webFilesMap[url].second = size;
-	}
+	return Storage::EncryptionKey(bytes::make_vector(LocalKey->data()));
 }
 
-class WebFileLoadTask : public Task {
-public:
-	WebFileLoadTask(const FileKey &key, const QString &url, webFileLoader *loader)
-		: _key(key)
-		, _url(url)
-		, _loader(loader)
-		, _result(0) {
-	}
-	void process() {
-		FileReadDescriptor image;
-		if (!readEncryptedFile(image, _key, FileOption::User)) {
-			return;
-		}
-
-		QByteArray imageData;
-		QString url;
-		image.stream >> url >> imageData;
-
-		_result = new Result(imageData);
-	}
-	void finish() {
-		if (_result) {
-			_loader->localLoaded(_result->image, _result->format, _result->pixmap);
-		} else {
-			WebFilesMap::iterator j = _webFilesMap.find(_url);
-			if (j != _webFilesMap.cend() && j->first == _key) {
-				clearKey(j.value().first, FileOption::User);
-				_storageWebFilesSize -= j.value().second;
-				_webFilesMap.erase(j);
-			}
-			_loader->localLoaded(StorageImageSaved());
-		}
-	}
-	virtual ~WebFileLoadTask() {
-		delete base::take(_result);
-	}
-
-protected:
-	FileKey _key;
-	QString _url;
-	struct Result {
-		explicit Result(const QByteArray &data) : image(data) {
-			QByteArray guessFormat;
-			pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false));
-			if (!pixmap.isNull()) {
-				format = guessFormat;
-			}
-		}
-		StorageImageSaved image;
-		QByteArray format;
-		QPixmap pixmap;
-
-	};
-	webFileLoader *_loader;
-	Result *_result;
-
-};
-
-TaskId startWebFileLoad(const QString &url, webFileLoader *loader) {
-	WebFilesMap::const_iterator j = _webFilesMap.constFind(url);
-	if (j == _webFilesMap.cend() || !_localLoader) {
-		return 0;
-	}
-	return _localLoader->addTask(
-		std::make_unique<WebFileLoadTask>(j->first, url, loader));
-}
-
-bool willWebFileLoad(const QString &url) {
-	return _webFilesMap.constFind(url) != _webFilesMap.cend();
-}
-
-int32 hasWebFiles() {
-	return _webFilesMap.size();
-}
-
-qint64 storageWebFilesSize() {
-	return _storageWebFilesSize;
-}
 
 class CountWaveformTask : public Task {
 public:
@@ -5136,8 +4688,6 @@ bool decrypt(const void *src, void *dst, uint32 len, const void *key128) {
 
 struct ClearManagerData {
 	QThread *thread;
-	StorageMap images, stickers, audios;
-	WebFilesMap webFiles;
 	QMutex mutex;
 	QList<int> tasks;
 	bool working;
@@ -5155,21 +4705,6 @@ bool ClearManager::addTask(int task) {
 	if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true;
 	if (task == ClearManagerAll) {
 		data->tasks.clear();
-		if (!_imagesMap.isEmpty()) {
-			_imagesMap.clear();
-			_storageImagesSize = 0;
-			_mapChanged = true;
-		}
-		if (!_stickerImagesMap.isEmpty()) {
-			_stickerImagesMap.clear();
-			_storageStickersSize = 0;
-			_mapChanged = true;
-		}
-		if (!_audiosMap.isEmpty()) {
-			_audiosMap.clear();
-			_storageAudiosSize = 0;
-			_mapChanged = true;
-		}
 		if (!_draftsMap.isEmpty()) {
 			_draftsMap.clear();
 			_mapChanged = true;
@@ -5208,73 +4743,6 @@ bool ClearManager::addTask(int task) {
 		}
 		_writeMap();
 	} else {
-		if (task & ClearManagerStorage) {
-			if (data->images.isEmpty()) {
-				data->images = _imagesMap;
-			} else {
-				for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) {
-					StorageKey k = i.key();
-					while (data->images.constFind(k) != data->images.cend()) {
-						++k.second;
-					}
-					data->images.insert(k, i.value());
-				}
-			}
-			if (!_imagesMap.isEmpty()) {
-				_imagesMap.clear();
-				_storageImagesSize = 0;
-				_mapChanged = true;
-			}
-			if (data->stickers.isEmpty()) {
-				data->stickers = _stickerImagesMap;
-			} else {
-				for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) {
-					StorageKey k = i.key();
-					while (data->stickers.constFind(k) != data->stickers.cend()) {
-						++k.second;
-					}
-					data->stickers.insert(k, i.value());
-				}
-			}
-			if (!_stickerImagesMap.isEmpty()) {
-				_stickerImagesMap.clear();
-				_storageStickersSize = 0;
-				_mapChanged = true;
-			}
-			if (data->webFiles.isEmpty()) {
-				data->webFiles = _webFilesMap;
-			} else {
-				for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) {
-					QString k = i.key();
-					while (data->webFiles.constFind(k) != data->webFiles.cend()) {
-						k += '#';
-					}
-					data->webFiles.insert(k, i.value());
-				}
-			}
-			if (!_webFilesMap.isEmpty()) {
-				_webFilesMap.clear();
-				_storageWebFilesSize = 0;
-				_writeLocations();
-			}
-			if (data->audios.isEmpty()) {
-				data->audios = _audiosMap;
-			} else {
-				for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) {
-					StorageKey k = i.key();
-					while (data->audios.constFind(k) != data->audios.cend()) {
-						++k.second;
-					}
-					data->audios.insert(k, i.value());
-				}
-			}
-			if (!_audiosMap.isEmpty()) {
-				_audiosMap.clear();
-				_storageAudiosSize = 0;
-				_mapChanged = true;
-			}
-			_writeMap();
-		}
 		for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
 			if (data->tasks.at(i) == task) return true;
 		}
@@ -5319,8 +4787,6 @@ void ClearManager::onStart() {
 	while (true) {
 		int task = 0;
 		bool result = false;
-		StorageMap images, stickers, audios;
-		WebFilesMap webFiles;
 		{
 			QMutexLocker lock(&data->mutex);
 			if (data->tasks.isEmpty()) {
@@ -5328,10 +4794,6 @@ void ClearManager::onStart() {
 				break;
 			}
 			task = data->tasks.at(0);
-			images = data->images;
-			stickers = data->stickers;
-			audios = data->audios;
-			webFiles = data->webFiles;
 		}
 		switch (task) {
 		case ClearManagerAll: {
@@ -5354,18 +4816,6 @@ void ClearManager::onStart() {
 			result = QDir(cTempDir()).removeRecursively();
 		break;
 		case ClearManagerStorage:
-			for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) {
-				clearKey(i.value().first, FileOption::User);
-			}
-			for (StorageMap::const_iterator i = stickers.cbegin(), e = stickers.cend(); i != e; ++i) {
-				clearKey(i.value().first, FileOption::User);
-			}
-			for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) {
-				clearKey(i.value().first, FileOption::User);
-			}
-			for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) {
-				clearKey(i.value().first, FileOption::User);
-			}
 			result = true;
 		break;
 		}
diff --git a/Telegram/SourceFiles/storage/localstorage.h b/Telegram/SourceFiles/storage/localstorage.h
index f6a10f9b9..03b291d9b 100644
--- a/Telegram/SourceFiles/storage/localstorage.h
+++ b/Telegram/SourceFiles/storage/localstorage.h
@@ -8,8 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "storage/file_download.h"
+#include "storage/cache/storage_cache_database.h"
+#include "storage/localimageloader.h"
 #include "auth_session.h"
 
+namespace Storage {
+class EncryptionKey;
+} // namespace Storage
+
 namespace Window {
 namespace Theme {
 struct Saved;
@@ -98,32 +104,9 @@ bool hasDraft(const PeerId &peer);
 void writeFileLocation(MediaKey location, const FileLocation &local);
 FileLocation readFileLocation(MediaKey location, bool check = true);
 
-void writeImage(const StorageKey &location, const ImagePtr &img);
-void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
-TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader);
-bool willImageLoad(const StorageKey &location);
-int32 hasImages();
-qint64 storageImagesSize();
-
-void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true);
-TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader);
-bool willStickerImageLoad(const StorageKey &location);
-bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation);
-int32 hasStickers();
-qint64 storageStickersSize();
-
-void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true);
-TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader);
-bool willAudioLoad(const StorageKey &location);
-bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation);
-int32 hasAudios();
-qint64 storageAudiosSize();
-
-void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true);
-TaskId startWebFileLoad(const QString &url, webFileLoader *loader);
-bool willWebFileLoad(const QString &url);
-int32 hasWebFiles();
-qint64 storageWebFilesSize();
+QString cachePath();
+Storage::Cache::Database::Settings cacheSettings();
+Storage::EncryptionKey cacheKey();
 
 void countVoiceWaveform(DocumentData *document);
 
diff --git a/Telegram/SourceFiles/ui/images.cpp b/Telegram/SourceFiles/ui/images.cpp
index 541e51081..f207e9fcd 100644
--- a/Telegram/SourceFiles/ui/images.cpp
+++ b/Telegram/SourceFiles/ui/images.cpp
@@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "mainwidget.h"
 #include "storage/localstorage.h"
+#include "storage/cache/storage_cache_database.h"
 #include "platform/platform_specific.h"
 #include "auth_session.h"
 #include "history/history_item.h"
 #include "history/history.h"
+#include "data/data_session.h"
 
 namespace Images {
 namespace {
@@ -915,6 +917,10 @@ void Image::restore() const {
 	_forgot = false;
 }
 
+base::optional<Storage::Cache::Key> Image::cacheKey() const {
+	return base::none;
+}
+
 void Image::invalidateSizeCache() const {
 	for (auto &pix : _sizesCache) {
 		if (!pix.isNull()) {
@@ -993,9 +999,9 @@ void RemoteImage::loadLocal() {
 	if (_loader) _loader->start();
 }
 
-void RemoteImage::setData(QByteArray &bytes, const QByteArray &bytesFormat) {
-	QBuffer buffer(&bytes);
-
+void RemoteImage::setImageBytes(
+		const QByteArray &bytes,
+		const QByteArray &bytesFormat) {
 	if (!_data.isNull()) {
 		globalAcquiredSize -= int64(_data.width()) * _data.height() * 4;
 	}
@@ -1013,6 +1019,11 @@ void RemoteImage::setData(QByteArray &bytes, const QByteArray &bytesFormat) {
 	_saved = bytes;
 	_format = fmt;
 	_forgot = false;
+
+	const auto location = this->location();
+	if (!location.isNull() && !bytes.isEmpty()) {
+		Auth().data().cache().putIfEmpty(Data::StorageCacheKey(location), bytes);
+	}
 }
 
 bool RemoteImage::amLoading() const {
@@ -1113,13 +1124,18 @@ StorageImage::StorageImage(const StorageImageLocation &location, int32 size)
 , _size(size) {
 }
 
-StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &bytes)
+StorageImage::StorageImage(
+	const StorageImageLocation &location,
+	const QByteArray &bytes)
 : _location(location)
 , _size(bytes.size()) {
-	setData(bytes);
-	if (!_location.isNull()) {
-		Local::writeImage(storageKey(_location), StorageImageSaved(bytes));
-	}
+	setImageBytes(bytes);
+}
+
+base::optional<Storage::Cache::Key> StorageImage::cacheKey() const {
+	return _location.isNull()
+		? base::none
+		: base::make_optional(Data::StorageCacheKey(_location));
 }
 
 int32 StorageImage::countWidth() const {
@@ -1130,10 +1146,6 @@ int32 StorageImage::countHeight() const {
 	return _location.height();
 }
 
-bool StorageImage::hasLocalCopy() const {
-	return Local::willImageLoad(storageKey(_location));
-}
-
 void StorageImage::setInformation(int32 size, int32 width, int32 height) {
 	_size = size;
 	_location.setSize(width, height);
@@ -1176,6 +1188,12 @@ WebFileImage::WebFileImage(
 , _size(size) {
 }
 
+base::optional<Storage::Cache::Key> WebFileImage::cacheKey() const {
+	return _location.isNull()
+		? base::none
+		: base::make_optional(Data::WebDocumentCacheKey(_location));
+}
+
 int WebFileImage::countWidth() const {
 	return _width;
 }
@@ -1184,10 +1202,6 @@ int WebFileImage::countHeight() const {
 	return _height;
 }
 
-bool WebFileImage::hasLocalCopy() const {
-	return Local::willImageLoad(storageKey(_location));
-}
-
 void WebFileImage::setInformation(int size, int width, int height) {
 	_size = size;
 	_width = width;
@@ -1219,13 +1233,13 @@ DelayedStorageImage::DelayedStorageImage(int32 w, int32 h)
 , _loadCancelled(false)
 , _loadFromCloud(false) {
 }
-
-DelayedStorageImage::DelayedStorageImage(QByteArray &bytes)
-: StorageImage(StorageImageLocation(), bytes)
-, _loadRequested(false)
-, _loadCancelled(false)
-, _loadFromCloud(false) {
-}
+//
+//DelayedStorageImage::DelayedStorageImage(QByteArray &bytes)
+//: StorageImage(StorageImageLocation(), bytes)
+//, _loadRequested(false)
+//, _loadCancelled(false)
+//, _loadFromCloud(false) {
+//}
 
 void DelayedStorageImage::setStorageLocation(
 		Data::FileOrigin origin,
@@ -1317,6 +1331,10 @@ WebImage::WebImage(const QString &url, int width, int height)
 , _height(height) {
 }
 
+base::optional<Storage::Cache::Key> WebImage::cacheKey() const {
+	return Data::UrlCacheKey(_url);
+}
+
 void WebImage::setSize(int width, int height) {
 	_width = width;
 	_height = height;
@@ -1330,10 +1348,6 @@ int32 WebImage::countHeight() const {
 	return _height;
 }
 
-bool WebImage::hasLocalCopy() const {
-	return Local::willWebFileLoad(_url);
-}
-
 void WebImage::setInformation(int32 size, int32 width, int32 height) {
 	_size = size;
 	setSize(width, height);
@@ -1404,8 +1418,8 @@ Image *getImage(int32 width, int32 height) {
 }
 
 StorageImage *getImage(const StorageImageLocation &location, int32 size) {
-	StorageKey key(storageKey(location));
-	StorageImages::const_iterator i = storageImages.constFind(key);
+	const auto key = storageKey(location);
+	auto i = storageImages.constFind(key);
 	if (i == storageImages.cend()) {
 		i = storageImages.insert(key, new StorageImage(location, size));
 	} else {
@@ -1414,20 +1428,17 @@ StorageImage *getImage(const StorageImageLocation &location, int32 size) {
 	return i.value();
 }
 
-StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes) {
-	StorageKey key(storageKey(location));
-	StorageImages::const_iterator i = storageImages.constFind(key);
+StorageImage *getImage(
+		const StorageImageLocation &location,
+		const QByteArray &bytes) {
+	const auto key = storageKey(location);
+	auto i = storageImages.constFind(key);
 	if (i == storageImages.cend()) {
-		QByteArray bytesArr(bytes);
-		i = storageImages.insert(key, new StorageImage(location, bytesArr));
+		i = storageImages.insert(key, new StorageImage(location, bytes));
 	} else {
 		i.value()->refreshFileReference(location.fileReference());
 		if (!i.value()->loaded()) {
-			QByteArray bytesArr(bytes);
-			i.value()->setData(bytesArr);
-			if (!location.isNull()) {
-				Local::writeImage(key, StorageImageSaved(bytes));
-			}
+			i.value()->setImageBytes(bytes);
 		}
 	}
 	return i.value();
diff --git a/Telegram/SourceFiles/ui/images.h b/Telegram/SourceFiles/ui/images.h
index 4eae6d1b2..d90d9a635 100644
--- a/Telegram/SourceFiles/ui/images.h
+++ b/Telegram/SourceFiles/ui/images.h
@@ -10,6 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/flags.h"
 #include "data/data_file_origin.h"
 
+namespace Storage {
+namespace Cache {
+struct Key;
+} // namespace Cache
+} // namespace Storage
+
 enum class ImageRoundRadius {
 	None,
 	Large,
@@ -372,6 +378,7 @@ public:
 	virtual const StorageImageLocation &location() const {
 		return StorageImageLocation::Null;
 	}
+	virtual base::optional<Storage::Cache::Key> cacheKey() const;
 
 	bool isNull() const;
 
@@ -412,10 +419,6 @@ protected:
 		return _data.height();
 	}
 
-	virtual bool hasLocalCopy() const {
-		return false;
-	}
-
 	mutable QByteArray _saved, _format;
 	mutable bool _forgot;
 	mutable QPixmap _data;
@@ -464,8 +467,8 @@ public:
 	float64 progress() const override;
 	int32 loadOffset() const override;
 
-	void setData(
-		QByteArray &bytes,
+	void setImageBytes(
+		const QByteArray &bytes,
 		const QByteArray &format = QByteArray());
 
 	void load(
@@ -507,12 +510,13 @@ private:
 
 class StorageImage : public RemoteImage {
 public:
-	StorageImage(const StorageImageLocation &location, int32 size = 0);
-	StorageImage(const StorageImageLocation &location, QByteArray &bytes);
+	explicit StorageImage(const StorageImageLocation &location, int32 size = 0);
+	StorageImage(const StorageImageLocation &location, const QByteArray &bytes);
 
 	const StorageImageLocation &location() const override {
 		return _location;
 	}
+	base::optional<Storage::Cache::Key> cacheKey() const override;
 	void refreshFileReference(const QByteArray &data) {
 		_location.refreshFileReference(data);
 	}
@@ -524,8 +528,6 @@ protected:
 		LoadFromCloudSetting fromCloud,
 		bool autoLoading) override;
 
-	bool hasLocalCopy() const override;
-
 	int32 countWidth() const override;
 	int32 countHeight() const override;
 
@@ -543,6 +545,8 @@ public:
 		int height,
 		int size = 0);
 
+	base::optional<Storage::Cache::Key> cacheKey() const override;
+
 protected:
 	void setInformation(int size, int width, int height) override;
 	FileLoader *createLoader(
@@ -554,8 +558,6 @@ protected:
 		return _box;
 	}
 
-	bool hasLocalCopy() const override;
-
 	int countWidth() const override;
 	int countHeight() const override;
 
@@ -571,7 +573,7 @@ class DelayedStorageImage : public StorageImage {
 public:
 	DelayedStorageImage();
 	DelayedStorageImage(int32 w, int32 h);
-	DelayedStorageImage(QByteArray &bytes);
+	//DelayedStorageImage(QByteArray &bytes);
 
 	void setStorageLocation(
 		Data::FileOrigin origin,
@@ -617,6 +619,8 @@ public:
 
 	void setSize(int width, int height);
 
+	base::optional<Storage::Cache::Key> cacheKey() const override;
+
 protected:
 	QSize shrinkBox() const override {
 		return _box;
@@ -630,8 +634,6 @@ protected:
 	int32 countWidth() const override;
 	int32 countHeight() const override;
 
-	bool hasLocalCopy() const override;
-
 private:
 	QString _url;
 	QSize _box;
@@ -652,7 +654,7 @@ Image *getImage(
 	const QPixmap &pixmap);
 Image *getImage(int32 width, int32 height);
 StorageImage *getImage(const StorageImageLocation &location, int size = 0);
-StorageImage *getImage(
+StorageImage *getImage( // photoCachedSize
 	const StorageImageLocation &location,
 	const QByteArray &bytes);
 Image *getImage(const MTPWebDocument &location);
diff --git a/Telegram/build/build.bat b/Telegram/build/build.bat
index 8d8be3af7..80d817611 100644
--- a/Telegram/build/build.bat
+++ b/Telegram/build/build.bat
@@ -250,6 +250,10 @@ if %BuildUWP% neq 0 (
   cd "%DeployPath%"
   7z a -mx9 %PortableFile% %BinaryName%\
   if %errorlevel% neq 0 goto error
+
+  move "%DeployPath%\%BinaryName%\%BinaryName%.exe" "%DeployPath%\"
+  rmdir "%DeployPath%\%BinaryName%"
+  if %errorlevel% neq 0 goto error
 )
 
 set "FinalDeployPath=%FinalReleasePath%\%AppVersionStrMajor%\%AppVersionStrFull%\tsetup"