Work with Cache::Database in LocalStorageBox.

This commit is contained in:
John Preston 2018-08-29 16:23:16 +03:00
parent 55f60866cb
commit 08ff324b1b
22 changed files with 796 additions and 131 deletions

View File

@ -355,14 +355,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_privacy" = "Privacy and Security";
"lng_local_storage_title" = "Local storage";
"lng_settings_no_data_cached" = "No cached data found!";
"lng_settings_images_cached#one" = "{count} image, {size}";
"lng_settings_images_cached#other" = "{count} images, {size}";
"lng_settings_audios_cached#one" = "{count} voice message, {size}";
"lng_settings_audios_cached#other" = "{count} voice messages, {size}";
"lng_local_storage_empty" = "No cached files";
"lng_local_storage_image#one" = "{count} image";
"lng_local_storage_image#other" = "{count} images";
"lng_local_storage_sticker#one" = "{count} sticker";
"lng_local_storage_sticker#other" = "{count} stickers";
"lng_local_storage_voice#one" = "{count} voice message";
"lng_local_storage_voice#other" = "{count} voice messages";
"lng_local_storage_round#one" = "{count} video message";
"lng_local_storage_round#other" = "{count} video messages";
"lng_local_storage_animation#one" = "{count} animation";
"lng_local_storage_animation#other" = "{count} animations";
"lng_local_storage_summary" = "Summary";
"lng_local_storage_clear_some" = "Clear";
"lng_local_storage_clear" = "Clear all";
"lng_local_storage_clearing" = "Clearing...";
"lng_local_storage_cleared" = "Cleared!";
"lng_settings_section_advanced_settings" = "Advanced Settings";
"lng_settings_enable_night_theme" = "Enable night mode";

View File

@ -307,6 +307,15 @@ public:
};
flat_multi_map() = default;
flat_multi_map(const flat_multi_map &other) = default;
flat_multi_map(flat_multi_map &&other) = default;
flat_multi_map &operator=(const flat_multi_map &other) {
auto copy = other;
return (*this = std::move(copy));
}
flat_multi_map &operator=(flat_multi_map &&other) = default;
size_type size() const {
return impl().size();
}

View File

@ -46,6 +46,27 @@ TEST_CASE("flat_maps should keep items sorted by key", "[flat_map]") {
}
}
TEST_CASE("simple flat_maps tests", "[flat_map]") {
SECTION("copy constructor") {
base::flat_map<int, string> v;
v.emplace(0, "a");
v.emplace(2, "b");
auto u = v;
REQUIRE(u.size() == 2);
REQUIRE(u.find(0) == u.begin());
REQUIRE(u.find(2) == u.end() - 1);
}
SECTION("assignment") {
base::flat_map<int, string> v, u;
v.emplace(0, "a");
v.emplace(2, "b");
u = v;
REQUIRE(u.size() == 2);
REQUIRE(u.find(0) == u.begin());
REQUIRE(u.find(2) == u.end() - 1);
}
}
TEST_CASE("flat_maps custom comparator", "[flat_map]") {
base::flat_map<int_wrap, string, int_wrap_comparator> v;
v.emplace({ 0 }, "a");

View File

@ -354,7 +354,27 @@ peerListBox: PeerList(defaultPeerList) {
}
}
localStorageBoxSkip: 10px;
localStorageRowHeight: 50px;
localStorageRowPadding: margins(23px, 5px, 23px, 5px);
localStorageRowTitle: FlatLabel(defaultFlatLabel) {
textFg: windowBoldFg;
maxHeight: 20px;
style: TextStyle(defaultTextStyle) {
font: font(14px semibold);
linkFont: font(14px semibold);
linkFontOver: font(14px semibold);
}
}
localStorageRowSize: FlatLabel(defaultFlatLabel) {
textFg: contactsStatusFg;
maxHeight: 20px;
style: TextStyle(defaultTextStyle) {
font: font(14px);
linkFont: font(14px);
linkFontOver: font(14px);
}
}
localStorageClear: defaultBoxButton;
shareRowsTop: 12px;
shareRowHeight: 108px;

View File

@ -8,15 +8,185 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/local_storage_box.h"
#include "styles/style_boxes.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/shadow.h"
#include "storage/localstorage.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "auth_session.h"
#include "layout.h"
LocalStorageBox::LocalStorageBox(QWidget *parent)
: _clear(this, lang(lng_local_storage_clear), st::boxLinkButton) {
class LocalStorageBox::Row : public Ui::RpWidget {
public:
Row(
QWidget *parent,
Fn<QString(size_type)> title,
Fn<QString()> clear,
const Database::TaggedSummary &data);
void update(const Database::TaggedSummary &data);
void toggleProgress(bool shown);
rpl::producer<> clearRequests() const;
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
QString titleText(const Database::TaggedSummary &data) const;
QString sizeText(const Database::TaggedSummary &data) const;
void step_radial(TimeMs ms, bool timer);
Fn<QString(size_type)> _titleFactory;
object_ptr<Ui::FlatLabel> _title;
object_ptr<Ui::FlatLabel> _description;
object_ptr<Ui::FlatLabel> _clearing = { nullptr };
object_ptr<Ui::RoundButton> _clear;
std::unique_ptr<Ui::InfiniteRadialAnimation> _progress;
};
LocalStorageBox::Row::Row(
QWidget *parent,
Fn<QString(size_type)> title,
Fn<QString()> clear,
const Database::TaggedSummary &data)
: RpWidget(parent)
, _titleFactory(std::move(title))
, _title(
this,
titleText(data),
Ui::FlatLabel::InitType::Simple,
st::localStorageRowTitle)
, _description(
this,
sizeText(data),
Ui::FlatLabel::InitType::Simple,
st::localStorageRowSize)
, _clear(this, std::move(clear), st::localStorageClear) {
_clear->setVisible(data.count != 0);
}
void LocalStorageBox::Row::update(const Database::TaggedSummary &data) {
if (data.count != 0) {
_title->setText(titleText(data));
}
_description->setText(sizeText(data));
_clear->setVisible(data.count != 0);
}
void LocalStorageBox::Row::toggleProgress(bool shown) {
if (!shown) {
_progress = nullptr;
_description->show();
_clearing.destroy();
} else if (!_progress) {
_progress = std::make_unique<Ui::InfiniteRadialAnimation>(
animation(this, &Row::step_radial),
st::proxyCheckingAnimation);
_progress->start();
_clearing = object_ptr<Ui::FlatLabel>(
this,
lang(lng_local_storage_clearing),
Ui::FlatLabel::InitType::Simple,
st::localStorageRowSize);
_clearing->show();
_description->hide();
resizeToWidth(width());
RpWidget::update();
}
}
void LocalStorageBox::Row::step_radial(TimeMs ms, bool timer) {
if (timer) {
RpWidget::update();
}
}
rpl::producer<> LocalStorageBox::Row::clearRequests() const {
return _clear->clicks();
}
int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
const auto height = st::localStorageRowHeight;
const auto padding = st::localStorageRowPadding;
const auto available = newWidth - padding.left() - padding.right();
_title->resizeToWidth(available);
_description->resizeToWidth(available);
_title->moveToLeft(padding.left(), padding.top(), newWidth);
_description->moveToLeft(
padding.left(),
height - padding.bottom() - _description->height(),
newWidth);
if (_clearing) {
const auto progressShift = st::proxyCheckingPosition.x()
+ st::proxyCheckingAnimation.size.width()
+ st::proxyCheckingSkip;
_clearing->resizeToWidth(available - progressShift);
_clearing->moveToLeft(
padding.left(),// + progressShift,
_description->y(),
newWidth);
}
_clear->moveToRight(
st::boxButtonPadding.right(),
(height - _clear->height()) / 2,
newWidth);
return height;
}
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
if (!_progress || true) {
return;
}
Painter p(this);
const auto padding = st::localStorageRowPadding;
const auto height = st::localStorageRowHeight;
const auto bottom = height - padding.bottom() - _description->height();
_progress->step(crl::time());
_progress->draw(
p,
{
st::proxyCheckingPosition.x() + padding.left(),
st::proxyCheckingPosition.y() + bottom
},
width());
}
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
return _titleFactory(data.count);
}
QString LocalStorageBox::Row::sizeText(const Database::TaggedSummary &data) const {
return data.totalSize
? formatSizeText(data.totalSize)
: lang(lng_local_storage_empty);
}
LocalStorageBox::LocalStorageBox(
QWidget*,
not_null<Database*> db,
CreateTag)
: _db(db) {
}
void LocalStorageBox::Show(not_null<Database*> db) {
auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
Box<LocalStorageBox>(db, CreateTag()));
const auto weak = shared->data();
db->statsOnMain(
) | rpl::start_with_next([=](Database::Stats &&stats) {
weak->update(std::move(stats));
if (auto &strong = *shared) {
Ui::show(std::move(strong));
}
}, weak->lifetime());
}
void LocalStorageBox::prepare() {
@ -24,27 +194,110 @@ void LocalStorageBox::prepare() {
addButton(langFactory(lng_box_ok), [this] { closeBox(); });
_clear->setClickedCallback([this] { clearStorage(); });
connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int)));
connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int)));
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
updateControls();
checkLocalStoredCounts();
setupControls();
}
void LocalStorageBox::updateControls() {
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::updateRow(
not_null<Ui::SlideWrap<Row>*> row,
Database::TaggedSummary *data) {
const auto summary = (_rows.find(0)->second == row);
const auto shown = (data && data->count && data->totalSize) || summary;
if (shown) {
row->entity()->update(*data);
}
row->toggle(shown, anim::type::normal);
}
void LocalStorageBox::checkLocalStoredCounts() {
void LocalStorageBox::update(Database::Stats &&stats) {
_stats = std::move(stats);
if (const auto i = _rows.find(0); i != end(_rows)) {
i->second->entity()->toggleProgress(_stats.clearing);
}
for (const auto &entry : _rows) {
if (entry.first) {
const auto i = _stats.tagged.find(entry.first);
updateRow(
entry.second,
(i != end(_stats.tagged)) ? &i->second : nullptr);
} else {
updateRow(entry.second, &_stats.full);
}
}
}
void LocalStorageBox::clearByTag(uint8 tag) {
if (tag) {
_db->clearByTag(tag);
} else {
_db->clear();
}
}
void LocalStorageBox::setupControls() {
_content.create(this);
const auto createRow = [&](
uint8 tag,
Fn<QString(size_type)> title,
Fn<QString()> clear,
const Database::TaggedSummary &data) {
auto result = _content->add(object_ptr<Ui::SlideWrap<Row>>(
_content,
object_ptr<Row>(
_content,
std::move(title),
std::move(clear),
data)));
const auto shown = (data.count && data.totalSize) || !tag;
result->toggle(shown, anim::type::instant);
result->entity()->clearRequests(
) | rpl::start_with_next([=] {
clearByTag(tag);
}, result->lifetime());
_rows.emplace(tag, result);
return result;
};
auto tracker = Ui::MultiSlideTracker();
const auto createTagRow = [&](uint8 tag, auto &&titleFactory) {
static const auto empty = Database::TaggedSummary();
const auto i = _stats.tagged.find(tag);
const auto &data = (i != end(_stats.tagged)) ? i->second : empty;
auto factory = std::forward<decltype(titleFactory)>(titleFactory);
auto title = [factory = std::move(factory)](size_type count) {
return factory(lt_count, count);
};
tracker.track(createRow(
tag,
std::move(title),
langFactory(lng_local_storage_clear_some),
data));
};
auto summaryTitle = [](size_type) {
return lang(lng_local_storage_summary);
};
createRow(
0,
std::move(summaryTitle),
langFactory(lng_local_storage_clear),
_stats.full);
const auto shadow = _content->add(object_ptr<Ui::SlideWrap<>>(
_content,
object_ptr<Ui::PlainShadow>(_content),
st::localStorageRowPadding)
);
createTagRow(Data::kImageCacheTag, lng_local_storage_image);
createTagRow(Data::kStickerCacheTag, lng_local_storage_sticker);
createTagRow(Data::kVoiceMessageCacheTag, lng_local_storage_voice);
createTagRow(Data::kVideoMessageCacheTag, lng_local_storage_round);
createTagRow(Data::kAnimationCacheTag, lng_local_storage_animation);
shadow->toggleOn(
std::move(tracker).atLeastOneShownValue()
);
_content->resizeToWidth(st::boxWidth);
_content->heightValue(
) | rpl::start_with_next([=](int height) {
setDimensions(st::boxWidth, height);
}, _content->lifetime());
}
void LocalStorageBox::paintEvent(QPaintEvent *e) {
@ -54,40 +307,4 @@ void LocalStorageBox::paintEvent(QPaintEvent *e) {
p.setFont(st::boxTextFont);
p.setPen(st::windowFg);
checkLocalStoredCounts();
auto top = 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);
case State::Cleared: return lang(lng_local_storage_cleared);
case State::ClearFailed: return Lang::Hard::ClearPathFailed();
}
return QString();
})();
if (!text.isEmpty()) {
p.drawTextLeft(st::boxPadding.left(), top, width(), text);
top += st::boxTextFont->height + st::localStorageBoxSkip;
}
}
void LocalStorageBox::clearStorage() {
App::wnd()->tempDirDelete(Local::ClearManagerStorage);
_state = State::Clearing;
updateControls();
}
void LocalStorageBox::onTempDirCleared(int task) {
if (task & Local::ClearManagerStorage) {
_state = State::Cleared;
}
updateControls();
}
void LocalStorageBox::onTempDirClearFailed(int task) {
if (task & Local::ClearManagerStorage) {
_state = State::ClearFailed;
}
updateControls();
}

View File

@ -8,20 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/abstract_box.h"
#include "storage/cache/storage_cache_database.h"
namespace Storage {
namespace Cache {
class Database;
} // namespace Cache
} // namespace Storage
namespace Ui {
class LinkButton;
class VerticalLayout;
template <typename Widget>
class SlideWrap;
} // namespace Ui
class LocalStorageBox : public BoxContent {
Q_OBJECT
struct CreateTag {
};
public:
LocalStorageBox(QWidget*);
using Database = Storage::Cache::Database;
private slots:
void onTempDirCleared(int task);
void onTempDirClearFailed(int task);
LocalStorageBox(QWidget*, not_null<Database*> db, CreateTag);
static void Show(not_null<Database*> db);
protected:
void prepare() override;
@ -29,18 +39,19 @@ protected:
void paintEvent(QPaintEvent *e) override;
private:
void clearStorage();
void updateControls();
void checkLocalStoredCounts();
class Row;
enum class State {
Normal,
Clearing,
Cleared,
ClearFailed,
};
State _state = State::Normal;
void clearByTag(uint8 tag);
void update(Database::Stats &&stats);
void updateRow(
not_null<Ui::SlideWrap<Row>*> row,
Database::TaggedSummary *data);
void setupControls();
object_ptr<Ui::LinkButton> _clear;
not_null<Storage::Cache::Database*> _db;
Database::Stats _stats;
object_ptr<Ui::VerticalLayout> _content = { nullptr };
base::flat_map<uint8, not_null<Ui::SlideWrap<Row>*>> _rows;
};

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "observer_peer.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "messenger.h"
#include "export/export_controller.h"
#include "export/view/export_view_panel_controller.h"
#include "window/notifications_manager.h"
@ -67,17 +68,19 @@ void UpdateImage(ImagePtr &old, ImagePtr now) {
Session::Session(not_null<AuthSession*> session)
: _session(session)
, _cache(Local::cachePath(), Local::cacheSettings())
, _cache(Messenger::Instance().databases().get(
Local::cachePath(),
Local::cacheSettings()))
, _groups(this)
, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
_cache.open(Local::cacheKey());
_cache->open(Local::cacheKey());
setupContactViewsViewer();
setupChannelLeavingViewer();
}
Storage::Cache::Database &Session::cache() {
return _cache;
return *_cache;
}
void Session::startExport(PeerData *peer) {

View File

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "storage/cache/storage_cache_database.h"
#include "storage/storage_databases.h"
#include "chat_helpers/stickers.h"
#include "dialogs/dialogs_key.h"
#include "data/data_groups.h"
@ -523,7 +523,7 @@ private:
not_null<AuthSession*> _session;
Storage::Cache::Database _cache;
Storage::DatabasePointer _cache;
std::unique_ptr<Export::ControllerWrap> _export;
std::unique_ptr<Export::View::PanelController> _exportPanel;

View File

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_form_controller.h"
#include "observer_peer.h"
#include "storage/file_upload.h"
#include "storage/storage_databases.h"
#include "mainwidget.h"
#include "mediaview.h"
#include "mtproto/dc_options.h"
@ -76,6 +77,7 @@ Messenger::Messenger(not_null<Core::Launcher*> launcher)
: QObject()
, _launcher(launcher)
, _private(std::make_unique<Private>())
, _databases(std::make_unique<Storage::Databases>())
, _langpack(std::make_unique<Lang::Instance>())
, _audio(std::make_unique<Media::Audio::Instance>())
, _logo(Window::LoadLogo())

View File

@ -19,6 +19,10 @@ class Translator;
class MediaView;
class BoxContent;
namespace Storage {
class Databases;
} // namespace Storage
namespace Core {
class Launcher;
} // namespace Core
@ -125,6 +129,11 @@ public:
void suggestMainDcId(MTP::DcId mainDcId);
void destroyStaleAuthorizationKeys();
// Databases
Storage::Databases &databases() {
return *_databases;
}
// AuthSession component.
AuthSession *authSession() {
return _authSession.get();
@ -249,6 +258,7 @@ private:
QWidget _globalShortcutParent;
std::unique_ptr<Storage::Databases> _databases;
std::unique_ptr<MainWindow> _window;
std::unique_ptr<MediaView> _mediaView;
std::unique_ptr<Lang::Instance> _langpack;

View File

@ -34,7 +34,7 @@ public:
void fire_copy(const Value &value) const {
return fire_forward(value);
}
#if defined _MSC_VER && _MSC_VER >= 1914
#if defined _MSC_VER && _MSC_VER >= 1914 && false
producer<Value> events() const {
#else // _MSC_VER >= 1914
auto events() const {
@ -65,6 +65,9 @@ public:
auto events_starting_with_copy(const Value &value) const {
return single(value) | then(events());
}
bool has_consumers() const {
return (_data != nullptr) && !_data->consumers.empty();
}
~event_stream();

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "boxes/about_box.h"
#include "boxes/local_storage_box.h"
#include "data/data_session.h"
#include "mainwindow.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
@ -85,7 +86,7 @@ void AdvancedWidget::checkNonDefaultTheme() {
}
void AdvancedWidget::onManageLocalStorage() {
Ui::show(Box<LocalStorageBox>());
LocalStorageBox::Show(&Auth().data().cache());
}
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY

View File

@ -16,6 +16,12 @@ Database::Database(const QString &path, const Settings &settings)
: _wrapped(path, settings) {
}
void Database::reconfigure(const Settings &settings) {
_wrapped.with([settings](Implementation &unwrapped) mutable {
unwrapped.reconfigure(settings);
});
}
void Database::open(EncryptionKey &&key, FnMut<void(Error)> &&done) {
_wrapped.with([
key = std::move(key),
@ -125,7 +131,7 @@ void Database::putIfEmpty(
void Database::getWithTag(
const Key &key,
FnMut<void(TaggedValue&&)> &&done) {
_wrapped.with([
_wrapped.with([
key,
done = std::move(done)
](Implementation &unwrapped) mutable {
@ -133,11 +139,9 @@ void Database::getWithTag(
});
}
void Database::stats(FnMut<void(Stats&&)> &&done) {
_wrapped.with([
done = std::move(done)
](Implementation &unwrapped) mutable {
unwrapped.stats(std::move(done));
auto Database::statsOnMain() const -> rpl::producer<Stats> {
return _wrapped.producer_on_main([](const Implementation &unwrapped) {
return unwrapped.stats();
});
}
@ -149,6 +153,15 @@ void Database::clear(FnMut<void(Error)> &&done) {
});
}
void Database::clearByTag(uint8 tag, FnMut<void(Error)> &&done) {
_wrapped.with([
tag,
done = std::move(done)
](Implementation &unwrapped) mutable {
unwrapped.clearByTag(tag, std::move(done));
});
}
Database::~Database() = default;
} // namespace Cache

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/basic_types.h"
#include <crl/crl_object_on_queue.h>
#include <crl/crl_time.h>
#include <rpl/producer.h>
#include <QtCore/QString>
namespace Storage {
@ -25,6 +26,8 @@ public:
using Settings = details::Settings;
Database(const QString &path, const Settings &settings);
void reconfigure(const Settings &settings);
void open(EncryptionKey &&key, FnMut<void(Error)> &&done = nullptr);
void close(FnMut<void()> &&done = nullptr);
@ -60,9 +63,11 @@ public:
void getWithTag(const Key &key, FnMut<void(TaggedValue&&)> &&done);
using Stats = details::Stats;
void stats(FnMut<void(Stats&&)> &&done);
using TaggedSummary = details::TaggedSummary;
rpl::producer<Stats> statsOnMain() const;
void clear(FnMut<void(Error)> &&done = nullptr);
void clearByTag(uint8 tag, FnMut<void(Error)> &&done = nullptr);
~Database();

View File

@ -82,6 +82,18 @@ DatabaseObject::DatabaseObject(
, _settings(settings)
, _writeBundlesTimer(_weak, [=] { writeBundles(); checkCompactor(); })
, _pruneTimer(_weak, [=] { prune(); }) {
checkSettings();
}
void DatabaseObject::reconfigure(const Settings &settings) {
Expects(_key.empty());
_settings = settings;
checkSettings();
}
void DatabaseObject::checkSettings() {
Expects(_settings.staleRemoveChunk > 0);
Expects(_settings.maxDataSize > 0
&& _settings.maxDataSize < kDataSizeLimit);
Expects(_settings.maxBundledRecords > 0
@ -124,7 +136,9 @@ Error DatabaseObject::openSomeBinlog(EncryptionKey &&key) {
case File::Result::LockFailed:
return Error{ Error::Type::LockFailed, binlogPath(version) };
case File::Result::WrongKey:
return Error{ Error::Type::WrongKey, binlogPath(version) };
return _settings.clearOnWrongKey
? openNewBinlog(key)
: Error{ Error::Type::WrongKey, binlogPath(version) };
}
Unexpected("Result from DatabaseObject::openBinlog.");
}
@ -290,8 +304,8 @@ bool DatabaseObject::startDelayedPruning() {
if (_settings.totalSizeLimit > 0
&& _totalSize > _settings.totalSizeLimit) {
return true;
} else if (_minimalEntryTime != 0
&& _minimalEntryTime <= before) {
} else if ((!_minimalEntryTime && !_map.empty())
|| _minimalEntryTime <= before) {
return true;
}
return false;
@ -315,17 +329,77 @@ bool DatabaseObject::startDelayedPruning() {
}
void DatabaseObject::prune() {
if (!_stale.empty()) {
return;
}
auto stale = base::flat_set<Key>();
auto staleTotalSize = int64();
collectTimePrune(stale, staleTotalSize);
collectSizePrune(stale, staleTotalSize);
collectTimeStale(stale, staleTotalSize);
collectSizeStale(stale, staleTotalSize);
if (stale.size() <= _settings.staleRemoveChunk) {
clearStaleNow(stale);
} else {
_stale = ranges::view::all(stale) | ranges::to_vector;
startStaleClear();
}
}
void DatabaseObject::startStaleClear() {
// Report "Clearing..." status.
pushStats();
clearStaleChunk();
}
void DatabaseObject::clearStaleNow(const base::flat_set<Key> &stale) {
if (stale.empty()) {
return;
}
// Report "Clearing..." status.
_stale.push_back(stale.front());
pushStats();
for (const auto &key : stale) {
remove(key, nullptr);
}
// Report correct status async.
_stale.clear();
optimize();
}
void DatabaseObject::collectTimePrune(
void DatabaseObject::clearStaleChunkDelayed() {
if (_clearingStale) {
return;
}
_clearingStale = true;
_weak.with([](DatabaseObject &that) {
if (base::take(that._clearingStale)) {
that.clearStaleChunk();
}
});
}
void DatabaseObject::clearStaleChunk() {
if (_stale.empty()) {
return;
}
const auto stale = gsl::make_span(_stale);
const auto count = size_type(stale.size());
const auto clear = std::min(count, _settings.staleRemoveChunk);
for (const auto &key : stale.subspan(count - clear)) {
remove(key, nullptr);
}
_stale.resize(count - clear);
if (_stale.empty()) {
base::take(_stale);
optimize();
} else {
clearStaleChunkDelayed();
}
}
void DatabaseObject::collectTimeStale(
base::flat_set<Key> &stale,
int64 &staleTotalSize) {
if (!_settings.totalTimeLimit) {
@ -351,7 +425,7 @@ void DatabaseObject::collectTimePrune(
}
}
void DatabaseObject::collectSizePrune(
void DatabaseObject::collectSizeStale(
base::flat_set<Key> &stale,
int64 &staleTotalSize) {
const auto removeSize = (_settings.totalSizeLimit > 0)
@ -516,7 +590,9 @@ void DatabaseObject::setMapEntry(const Key &key, Entry &&entry) {
++_entriesWithMinimalTimeCount;
} else if (already.useTime == _minimalEntryTime) {
Assert(_entriesWithMinimalTimeCount > 0);
--_entriesWithMinimalTimeCount;
if (!--_entriesWithMinimalTimeCount) {
_minimalEntryTime = 0;
}
}
}
already = std::move(entry);
@ -542,6 +618,25 @@ void DatabaseObject::updateStats(const Entry &was, const Entry &now) {
summary.totalSize -= was.size;
}
}
pushStatsDelayed();
}
void DatabaseObject::pushStatsDelayed() {
if (_pushingStats) {
return;
}
_pushingStats = true;
_weak.with([](DatabaseObject &that) {
if (base::take(that._pushingStats)) {
that.pushStats();
}
});
}
void DatabaseObject::pushStats() {
if (_stats.has_consumers()) {
_stats.fire(collectStats());
}
}
void DatabaseObject::eraseMapEntry(const Map::const_iterator &i) {
@ -550,7 +645,9 @@ void DatabaseObject::eraseMapEntry(const Map::const_iterator &i) {
updateStats(entry, Entry());
if (_minimalEntryTime != 0 && entry.useTime == _minimalEntryTime) {
Assert(_entriesWithMinimalTimeCount > 0);
--_entriesWithMinimalTimeCount;
if (!--_entriesWithMinimalTimeCount) {
_minimalEntryTime = 0;
}
}
_map.erase(i);
}
@ -629,14 +726,29 @@ void DatabaseObject::compactorFail() {
void DatabaseObject::close(FnMut<void()> &&done) {
if (_binlog.isOpen()) {
writeBundles();
_binlog.close();
}
invokeCallback(done);
clearState();
}
void DatabaseObject::clearState() {
_path = QString();
_key = {};
_map = {};
_removing = {};
_accessed = {};
_time = {};
_binlogExcessLength = 0;
_totalSize = 0;
_minimalEntryTime = 0;
_entriesWithMinimalTimeCount = 0;
_taggedStats = {};
_pushingStats = false;
_writeBundlesTimer.cancel();
_pruneTimer.cancel();
_cleaner = CleanerWrap();
_compactor = CompactorWrap();
_binlog.close();
_key = EncryptionKey();
invokeCallback(done);
_map.clear();
_binlogExcessLength = 0;
}
void DatabaseObject::put(
@ -648,6 +760,7 @@ void DatabaseObject::put(
return;
}
_removing.erase(key);
_stale.erase(ranges::remove(_stale, key), end(_stale));
const auto checksum = CountChecksum(bytes::make_span(value.bytes));
const auto maybepath = writeKeyPlace(key, value, checksum);
@ -762,6 +875,7 @@ Error DatabaseObject::writeExistingPlaceGeneric(
const Key &key,
const Entry &entry) {
record.key = key;
record.tag = entry.tag;
record.size = ReadTo<EntrySize>(entry.size);
record.checksum = entry.checksum;
if (const auto i = _map.find(key); i != end(_map)) {
@ -927,20 +1041,22 @@ void DatabaseObject::moveIfEmpty(
invokeCallback(done, result);
return;
}
_removing.erase(to);
_stale.erase(ranges::remove(_stale, to), end(_stale));
invokeCallback(done, writeExistingPlace(to, entry));
}
void DatabaseObject::stats(FnMut<void(Stats&&)> &&done) {
auto result = _taggedStats;
auto zero = TaggedSummary();
zero.count = _map.size();
zero.totalSize = _totalSize;
for (const auto &summary : result) {
zero.count -= summary.second.count;
zero.totalSize -= summary.second.totalSize;
}
result[0] = zero;
invokeCallback(done, std::move(result));
rpl::producer<Stats> DatabaseObject::stats() const {
return _stats.events_starting_with(collectStats());
}
Stats DatabaseObject::collectStats() const {
auto result = Stats();
result.tagged = _taggedStats;
result.full.count = _map.size();
result.full.totalSize = _totalSize;
result.clearing = (_cleaner.object != nullptr) || !_stale.empty();
return result;
}
void DatabaseObject::writeBundlesLazy() {
@ -1047,10 +1163,12 @@ void DatabaseObject::createCleaner() {
_base,
std::move(right),
std::move(done));
pushStatsDelayed();
}
void DatabaseObject::cleanerDone(Error error) {
_cleaner = CleanerWrap();
pushStatsDelayed();
}
void DatabaseObject::checkCompactor() {
@ -1079,12 +1197,33 @@ void DatabaseObject::checkCompactor() {
}
void DatabaseObject::clear(FnMut<void(Error)> &&done) {
Expects(_key.empty());
auto key = std::move(_key);
if (!key.empty()) {
close(nullptr);
}
const auto version = findAvailableVersion();
invokeCallback(
done,
writeVersion(version) ? Error::NoError() : ioError(versionPath()));
if (!writeVersion(version)) {
invokeCallback(done, ioError(versionPath()));
return;
}
if (key.empty()) {
invokeCallback(done, Error::NoError());
return;
}
open(std::move(key), std::move(done));
}
void DatabaseObject::clearByTag(uint8 tag, FnMut<void(Error)> &&done) {
const auto hadStale = !_stale.empty();
for (const auto &[key, entry] : _map) {
if (entry.tag == tag) {
_stale.push_back(key);
}
}
if (!hadStale) {
startStaleClear();
}
invokeCallback(done, Error::NoError());
}
auto DatabaseObject::getManyRaw(const std::vector<Key> &keys) const

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h"
#include "base/flat_set.h"
#include <set>
#include <rpl/event_stream.h>
namespace Storage {
namespace Cache {
@ -29,6 +30,7 @@ public:
crl::weak_on_queue<DatabaseObject> weak,
const QString &path,
const Settings &settings);
void reconfigure(const Settings &settings);
void open(EncryptionKey &&key, FnMut<void(Error)> &&done);
void close(FnMut<void()> &&done);
@ -53,9 +55,10 @@ public:
const Key &to,
FnMut<void(Error)> &&done);
void stats(FnMut<void(Stats&&)> &&done);
rpl::producer<Stats> stats() const;
void clear(FnMut<void(Error)> &&done);
void clearByTag(uint8 tag, FnMut<void(Error)> &&done);
static QString BinlogFilename();
static QString CompactReadyFilename();
@ -101,6 +104,7 @@ private:
Error ioError(const QString &path) const;
void checkSettings();
QString computePath(Version version) const;
QString binlogPath(Version version) const;
QString binlogPath() const;
@ -146,15 +150,24 @@ private:
uint64 countRelativeTime() const;
EstimatedTimePoint countTimePoint() const;
void applyTimePoint(EstimatedTimePoint time);
uint64 pruneBeforeTime() const;
void prune();
void collectTimePrune(
void collectTimeStale(
base::flat_set<Key> &stale,
int64 &staleTotalSize);
void collectSizePrune(
void collectSizeStale(
base::flat_set<Key> &stale,
int64 &staleTotalSize);
void startStaleClear();
void clearStaleNow(const base::flat_set<Key> &stale);
void clearStaleChunkDelayed();
void clearStaleChunk();
void updateStats(const Entry &was, const Entry &now);
Stats collectStats() const;
void pushStatsDelayed();
void pushStats();
void setMapEntry(const Key &key, Entry &&entry);
void eraseMapEntry(const Map::const_iterator &i);
@ -197,15 +210,17 @@ private:
void createCleaner();
void cleanerDone(Error error);
void clearState();
crl::weak_on_queue<DatabaseObject> _weak;
QString _base, _path;
const Settings _settings;
Settings _settings;
EncryptionKey _key;
File _binlog;
Map _map;
std::set<Key> _removing;
std::set<Key> _accessed;
std::vector<Key> _stale;
EstimatedTimePoint _time;
@ -214,7 +229,10 @@ private:
uint64 _minimalEntryTime = 0;
size_type _entriesWithMinimalTimeCount = 0;
Stats _taggedStats;
base::flat_map<uint8, TaggedSummary> _taggedStats;
rpl::event_stream<Stats> _stats;
bool _pushingStats = false;
bool _clearingStale = false;
base::ConcurrentTimer _writeBundlesTimer;
base::ConcurrentTimer _pruneTimer;

View File

@ -56,6 +56,7 @@ struct Settings {
size_type readBlockSize = 8 * 1024 * 1024;
size_type maxDataSize = 10 * 1024 * 1024;
crl::time_type writeBundleDelay = 15 * 60 * crl::time_type(1000);
size_type staleRemoveChunk = 256;
int64 compactAfterExcess = 8 * 1024 * 1024;
int64 compactAfterFullSize = 0;
@ -66,6 +67,8 @@ struct Settings {
size_type totalTimeLimit = 30 * 86400; // One month in seconds.
crl::time_type pruneTimeout = 5 * crl::time_type(1000);
crl::time_type maxPruneCheckTimeout = 3600 * crl::time_type(1000);
bool clearOnWrongKey = false;
};
struct TaggedValue {
@ -80,7 +83,11 @@ struct TaggedSummary {
size_type count = 0;
size_type totalSize = 0;
};
using Stats = base::flat_map<uint8, TaggedSummary>;
struct Stats {
TaggedSummary full;
base::flat_map<uint8, TaggedSummary> tagged;
bool clearing = false;
};
using Version = int32;

View File

@ -2949,6 +2949,7 @@ QString cachePath() {
Storage::Cache::Database::Settings cacheSettings() {
auto result = Storage::Cache::Database::Settings();
result.clearOnWrongKey = true;
return result;
}
@ -2958,7 +2959,6 @@ Storage::EncryptionKey cacheKey() {
return Storage::EncryptionKey(bytes::make_vector(LocalKey->data()));
}
class CountWaveformTask : public Task {
public:
CountWaveformTask(DocumentData *doc)

View File

@ -0,0 +1,103 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "storage/storage_databases.h"
#include "storage/cache/storage_cache_database.h"
namespace Storage {
DatabasePointer::DatabasePointer(
not_null<Databases*> owner,
const std::unique_ptr<Cache::Database> &value)
: _owner(owner)
, _value(value.get()) {
}
DatabasePointer::DatabasePointer(DatabasePointer &&other)
: _value(base::take(other._value))
, _owner(other._owner) {
}
DatabasePointer &DatabasePointer::operator=(DatabasePointer &&other) {
if (this != &other) {
destroy();
_owner = other._owner;
_value = base::take(other._value);
}
return *this;
}
DatabasePointer::~DatabasePointer() {
destroy();
}
Cache::Database *DatabasePointer::get() const {
return _value;
}
Cache::Database &DatabasePointer::operator*() const {
Expects(_value != nullptr);
return *get();
}
Cache::Database *DatabasePointer::operator->() const {
Expects(_value != nullptr);
return get();
}
DatabasePointer::operator bool() const {
return get() != nullptr;
}
void DatabasePointer::destroy() {
if (const auto value = base::take(_value)) {
_owner->destroy(value);
}
}
Databases::Kept::Kept(std::unique_ptr<Cache::Database> &&database)
: database(std::move(database)) {
}
DatabasePointer Databases::get(
const QString &path,
const Cache::details::Settings &settings) {
if (const auto i = _map.find(path); i != end(_map)) {
auto &kept = i->second;
Assert(kept.destroying.alive());
kept.destroying.kill();
kept.database->reconfigure(settings);
return DatabasePointer(this, kept.database);
}
const auto [i, ok] = _map.emplace(
path,
std::make_unique<Cache::Database>(path, settings));
return DatabasePointer(this, i->second.database);
}
void Databases::destroy(Cache::Database *database) {
for (auto &[path, kept] : _map) {
if (kept.database.get() == database) {
Assert(!kept.destroying.alive());
auto [first, second] = base::make_binary_guard();
kept.destroying = std::move(first);
database->close([=, guard = std::move(second)]() mutable {
crl::on_main([=, guard = std::move(guard)]{
if (!guard.alive()) {
return;
}
_map.erase(path);
});
});
}
}
}
} // namespace Storage

View File

@ -0,0 +1,71 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "storage/cache/storage_cache_database.h"
#include "base/binary_guard.h"
namespace Storage {
namespace Cache {
namespace details {
struct Settings;
} // namespace details
class Database;
} // namespace Cache
class Databases;
class DatabasePointer {
public:
DatabasePointer(const DatabasePointer &other) = delete;
DatabasePointer(DatabasePointer &&other);
DatabasePointer &operator=(const DatabasePointer &other) = delete;
DatabasePointer &operator=(DatabasePointer &&other);
~DatabasePointer();
Cache::Database *get() const;
Cache::Database &operator*() const;
Cache::Database *operator->() const;
explicit operator bool() const;
private:
friend class Databases;
DatabasePointer(
not_null<Databases*> owner,
const std::unique_ptr<Cache::Database> &value);
void destroy();
Cache::Database *_value = nullptr;
not_null<Databases*> _owner;
};
class Databases {
public:
DatabasePointer get(
const QString &path,
const Cache::details::Settings &settings);
private:
friend class DatabasePointer;
struct Kept {
Kept(std::unique_ptr<Cache::Database> &&database);
std::unique_ptr<Cache::Database> database;
base::binary_guard destroying;
};
void destroy(Cache::Database *database);
std::map<QString, Kept> _map;
};
} // namespace Storage

View File

@ -100,7 +100,10 @@ private:
class RoundButton : public RippleButton, private base::Subscriber {
public:
RoundButton(QWidget *parent, Fn<QString()> textFactory, const style::RoundButton &st);
RoundButton(
QWidget *parent,
Fn<QString()> textFactory,
const style::RoundButton &st);
void setText(Fn<QString()> textFactory);

View File

@ -50,6 +50,8 @@
'<(submodules_loc)/xxHash',
],
'sources': [
'<(src_loc)/storage/storage_databases.cpp',
'<(src_loc)/storage/storage_databases.h',
'<(src_loc)/storage/storage_encryption.cpp',
'<(src_loc)/storage/storage_encryption.h',
'<(src_loc)/storage/storage_encrypted_file.cpp',