local_storage_box.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/local_storage_box.h"
  8. #include "ui/wrap/vertical_layout.h"
  9. #include "ui/wrap/slide_wrap.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/widgets/buttons.h"
  12. #include "ui/widgets/shadow.h"
  13. #include "ui/widgets/continuous_sliders.h"
  14. #include "ui/effects/radial_animation.h"
  15. #include "ui/text/format_values.h"
  16. #include "ui/emoji_config.h"
  17. #include "storage/storage_account.h"
  18. #include "storage/cache/storage_cache_database.h"
  19. #include "data/data_session.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "window/window_session_controller.h"
  23. #include "styles/style_layers.h"
  24. #include "styles/style_boxes.h"
  25. namespace {
  26. constexpr auto kMegabyte = int64(1024 * 1024);
  27. constexpr auto kTotalSizeLimitsCount = 18;
  28. constexpr auto kMediaSizeLimitsCount = 18;
  29. constexpr auto kMinimalSizeLimit = 100 * kMegabyte;
  30. constexpr auto kTimeLimitsCount = 16;
  31. constexpr auto kMaxTimeLimitValue = std::numeric_limits<size_type>::max();
  32. constexpr auto kFakeMediaCacheTag = uint16(0xFFFF);
  33. int64 TotalSizeLimitInMB(int index) {
  34. if (index < 8) {
  35. return int64(index + 2) * 100;
  36. }
  37. return int64(index - 7) * 1024;
  38. }
  39. int64 TotalSizeLimit(int index) {
  40. return TotalSizeLimitInMB(index) * kMegabyte;
  41. }
  42. int64 MediaSizeLimitInMB(int index) {
  43. if (index < 9) {
  44. return int64(index + 1) * 100;
  45. }
  46. return int64(index - 8) * 1024;
  47. }
  48. int64 MediaSizeLimit(int index) {
  49. return MediaSizeLimitInMB(index) * kMegabyte;
  50. }
  51. QString SizeLimitText(int64 limit) {
  52. const auto mb = (limit / (1024 * 1024));
  53. const auto gb = (mb / 1024);
  54. return (gb > 0)
  55. ? (QString::number(gb) + " GB")
  56. : (QString::number(mb) + " MB");
  57. }
  58. size_type TimeLimitInDays(int index) {
  59. if (index < 3) {
  60. const auto weeks = (index + 1);
  61. return size_type(weeks) * 7;
  62. } else if (index < 15) {
  63. const auto month = (index - 2);
  64. return (size_type(month) * 30)
  65. + ((month >= 12) ? 5 :
  66. (month >= 10) ? 4 :
  67. (month >= 8) ? 3 :
  68. (month >= 7) ? 2 :
  69. (month >= 5) ? 1 :
  70. (month >= 3) ? 0 :
  71. (month >= 2) ? -1 :
  72. (month >= 1) ? 1 : 0);
  73. //+ (month >= 1 ? 1 : 0)
  74. //- (month >= 2 ? 2 : 0)
  75. //+ (month >= 3 ? 1 : 0)
  76. //+ (month >= 5 ? 1 : 0)
  77. //+ (month >= 7 ? 1 : 0)
  78. //+ (month >= 8 ? 1 : 0)
  79. //+ (month >= 10 ? 1 : 0)
  80. //+ (month >= 12 ? 1 : 0);
  81. }
  82. return 0;
  83. }
  84. size_type TimeLimit(int index) {
  85. const auto days = TimeLimitInDays(index);
  86. return days
  87. ? (days * 24 * 60 * 60)
  88. : kMaxTimeLimitValue;
  89. }
  90. QString TimeLimitText(size_type limit) {
  91. const auto days = (limit / (24 * 60 * 60));
  92. const auto weeks = (days / 7);
  93. const auto months = (days / 29);
  94. return (months > 0)
  95. ? tr::lng_months(tr::now, lt_count, months)
  96. : (limit > 0)
  97. ? tr::lng_weeks(tr::now, lt_count, weeks)
  98. : tr::lng_local_storage_limit_never(tr::now);
  99. }
  100. size_type LimitToValue(size_type timeLimit) {
  101. return timeLimit ? timeLimit : kMaxTimeLimitValue;
  102. }
  103. size_type ValueToLimit(size_type timeLimit) {
  104. return (timeLimit != kMaxTimeLimitValue) ? timeLimit : 0;
  105. }
  106. } // namespace
  107. class LocalStorageBox::Row : public Ui::RpWidget {
  108. public:
  109. Row(
  110. QWidget *parent,
  111. Fn<QString(size_type)> title,
  112. rpl::producer<QString> clear,
  113. const Database::TaggedSummary &data);
  114. void update(const Database::TaggedSummary &data);
  115. void toggleProgress(bool shown);
  116. rpl::producer<> clearRequests() const;
  117. protected:
  118. int resizeGetHeight(int newWidth) override;
  119. void paintEvent(QPaintEvent *e) override;
  120. private:
  121. QString titleText(const Database::TaggedSummary &data) const;
  122. QString sizeText(const Database::TaggedSummary &data) const;
  123. void radialAnimationCallback();
  124. Fn<QString(size_type)> _titleFactory;
  125. object_ptr<Ui::FlatLabel> _title;
  126. object_ptr<Ui::FlatLabel> _description;
  127. object_ptr<Ui::FlatLabel> _clearing = { nullptr };
  128. object_ptr<Ui::RoundButton> _clear;
  129. std::unique_ptr<Ui::InfiniteRadialAnimation> _progress;
  130. };
  131. LocalStorageBox::Row::Row(
  132. QWidget *parent,
  133. Fn<QString(size_type)> title,
  134. rpl::producer<QString> clear,
  135. const Database::TaggedSummary &data)
  136. : RpWidget(parent)
  137. , _titleFactory(std::move(title))
  138. , _title(
  139. this,
  140. titleText(data),
  141. st::localStorageRowTitle)
  142. , _description(
  143. this,
  144. sizeText(data),
  145. st::localStorageRowSize)
  146. , _clear(this, std::move(clear), st::localStorageClear) {
  147. _clear->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  148. _clear->setVisible(data.count != 0);
  149. }
  150. void LocalStorageBox::Row::update(const Database::TaggedSummary &data) {
  151. if (data.count != 0) {
  152. _title->setText(titleText(data));
  153. }
  154. _description->setText(sizeText(data));
  155. _clear->setVisible(data.count != 0);
  156. }
  157. void LocalStorageBox::Row::toggleProgress(bool shown) {
  158. if (!shown) {
  159. _progress = nullptr;
  160. _description->show();
  161. _clearing.destroy();
  162. } else if (!_progress) {
  163. _progress = std::make_unique<Ui::InfiniteRadialAnimation>(
  164. [=] { radialAnimationCallback(); },
  165. st::proxyCheckingAnimation);
  166. _progress->start();
  167. _clearing = object_ptr<Ui::FlatLabel>(
  168. this,
  169. tr::lng_local_storage_clearing(tr::now),
  170. st::localStorageRowSize);
  171. _clearing->show();
  172. _description->hide();
  173. resizeToWidth(width());
  174. RpWidget::update();
  175. }
  176. }
  177. void LocalStorageBox::Row::radialAnimationCallback() {
  178. if (!anim::Disabled()) {
  179. RpWidget::update();
  180. }
  181. }
  182. rpl::producer<> LocalStorageBox::Row::clearRequests() const {
  183. return _clear->clicks() | rpl::to_empty;
  184. }
  185. int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
  186. const auto height = st::localStorageRowHeight;
  187. const auto padding = st::localStorageRowPadding;
  188. const auto available = newWidth - padding.left() - padding.right();
  189. _title->resizeToWidth(available);
  190. _description->resizeToWidth(available);
  191. _title->moveToLeft(padding.left(), padding.top(), newWidth);
  192. _description->moveToLeft(
  193. padding.left(),
  194. height - padding.bottom() - _description->height(),
  195. newWidth);
  196. if (_clearing) {
  197. const auto progressShift = st::proxyCheckingPosition.x()
  198. + st::proxyCheckingAnimation.size.width()
  199. + st::proxyCheckingSkip;
  200. _clearing->resizeToWidth(available - progressShift);
  201. _clearing->moveToLeft(
  202. padding.left(),// + progressShift,
  203. _description->y(),
  204. newWidth);
  205. }
  206. _clear->moveToRight(
  207. st::layerBox.buttonPadding.right(),
  208. (height - _clear->height()) / 2,
  209. newWidth);
  210. return height;
  211. }
  212. void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
  213. #if 0 // not used
  214. if (!_progress) {
  215. return;
  216. }
  217. auto p = QPainter(this);
  218. const auto padding = st::localStorageRowPadding;
  219. const auto height = st::localStorageRowHeight;
  220. const auto bottom = height - padding.bottom() - _description->height();
  221. _progress->draw(
  222. p,
  223. {
  224. st::proxyCheckingPosition.x() + padding.left(),
  225. st::proxyCheckingPosition.y() + bottom
  226. },
  227. width());
  228. #endif
  229. }
  230. QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
  231. return _titleFactory(data.count);
  232. }
  233. QString LocalStorageBox::Row::sizeText(const Database::TaggedSummary &data) const {
  234. return data.totalSize
  235. ? Ui::FormatSizeText(data.totalSize)
  236. : tr::lng_local_storage_empty(tr::now);
  237. }
  238. LocalStorageBox::LocalStorageBox(
  239. QWidget*,
  240. not_null<Main::Session*> session,
  241. CreateTag)
  242. : _session(session)
  243. , _db(&session->data().cache())
  244. , _dbBig(&session->data().cacheBigFile()) {
  245. const auto &settings = session->local().cacheSettings();
  246. const auto &settingsBig = session->local().cacheBigFileSettings();
  247. _totalSizeLimit = settings.totalSizeLimit + settingsBig.totalSizeLimit;
  248. _mediaSizeLimit = settingsBig.totalSizeLimit;
  249. _timeLimit = settings.totalTimeLimit;
  250. }
  251. void LocalStorageBox::Show(not_null<Window::SessionController*> controller) {
  252. auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
  253. Box<LocalStorageBox>(&controller->session(), CreateTag()));
  254. const auto weak = shared->data();
  255. rpl::combine(
  256. controller->session().data().cache().statsOnMain(),
  257. controller->session().data().cacheBigFile().statsOnMain()
  258. ) | rpl::start_with_next([=](
  259. Database::Stats &&stats,
  260. Database::Stats &&statsBig) {
  261. weak->update(std::move(stats), std::move(statsBig));
  262. if (auto &strong = *shared) {
  263. controller->uiShow()->show(std::move(strong));
  264. }
  265. }, weak->lifetime());
  266. }
  267. void LocalStorageBox::prepare() {
  268. setTitle(tr::lng_local_storage_title());
  269. addButton(tr::lng_box_ok(), [this] { closeBox(); });
  270. setupControls();
  271. }
  272. void LocalStorageBox::updateRow(
  273. not_null<Ui::SlideWrap<Row>*> row,
  274. const Database::TaggedSummary *data) {
  275. const auto summary = (_rows.find(0)->second == row);
  276. const auto shown = (data && data->count && data->totalSize) || summary;
  277. if (shown) {
  278. row->entity()->update(*data);
  279. }
  280. row->toggle(shown, anim::type::normal);
  281. }
  282. void LocalStorageBox::update(
  283. Database::Stats &&stats,
  284. Database::Stats &&statsBig) {
  285. _stats = std::move(stats);
  286. _statsBig = std::move(statsBig);
  287. if (const auto i = _rows.find(0); i != end(_rows)) {
  288. i->second->entity()->toggleProgress(
  289. _stats.clearing || _statsBig.clearing);
  290. }
  291. for (const auto &entry : _rows) {
  292. if (entry.first == kFakeMediaCacheTag) {
  293. updateRow(entry.second, &_statsBig.full);
  294. } else if (entry.first) {
  295. const auto i = _stats.tagged.find(entry.first);
  296. updateRow(
  297. entry.second,
  298. (i != end(_stats.tagged)) ? &i->second : nullptr);
  299. } else {
  300. const auto full = summary();
  301. updateRow(entry.second, &full);
  302. }
  303. }
  304. }
  305. auto LocalStorageBox::summary() const -> Database::TaggedSummary {
  306. auto result = _stats.full;
  307. result.count += _statsBig.full.count;
  308. result.totalSize += _statsBig.full.totalSize;
  309. return result;
  310. }
  311. void LocalStorageBox::clearByTag(uint16 tag) {
  312. if (tag == kFakeMediaCacheTag) {
  313. _dbBig->clear();
  314. } else if (tag) {
  315. _db->clearByTag(tag);
  316. } else {
  317. _db->clear();
  318. _dbBig->clear();
  319. Ui::Emoji::ClearIrrelevantCache();
  320. }
  321. }
  322. void LocalStorageBox::setupControls() {
  323. const auto container = setInnerWidget(
  324. object_ptr<Ui::VerticalLayout>(this),
  325. st::defaultMultiSelect.scroll);
  326. const auto createRow = [&](
  327. uint16 tag,
  328. Fn<QString(size_type)> title,
  329. rpl::producer<QString> clear,
  330. const Database::TaggedSummary &data) {
  331. auto result = container->add(object_ptr<Ui::SlideWrap<Row>>(
  332. container,
  333. object_ptr<Row>(
  334. container,
  335. std::move(title),
  336. std::move(clear),
  337. data)));
  338. const auto shown = (data.count && data.totalSize) || !tag;
  339. result->toggle(shown, anim::type::instant);
  340. result->entity()->clearRequests(
  341. ) | rpl::start_with_next([=] {
  342. clearByTag(tag);
  343. }, result->lifetime());
  344. _rows.emplace(tag, result);
  345. return result;
  346. };
  347. auto tracker = Ui::MultiSlideTracker();
  348. const auto createTagRow = [&](uint8 tag, auto &&titleFactory) {
  349. static const auto empty = Database::TaggedSummary();
  350. const auto i = _stats.tagged.find(tag);
  351. const auto &data = (i != end(_stats.tagged)) ? i->second : empty;
  352. auto factory = std::forward<decltype(titleFactory)>(titleFactory);
  353. auto title = [factory = std::move(factory)](size_type count) {
  354. return factory(tr::now, lt_count, count);
  355. };
  356. tracker.track(createRow(
  357. tag,
  358. std::move(title),
  359. tr::lng_local_storage_clear_some(),
  360. data));
  361. };
  362. auto summaryTitle = [](size_type) {
  363. return tr::lng_local_storage_summary(tr::now);
  364. };
  365. auto mediaCacheTitle = [](size_type) {
  366. return tr::lng_local_storage_media(tr::now);
  367. };
  368. createRow(
  369. 0,
  370. std::move(summaryTitle),
  371. tr::lng_local_storage_clear(),
  372. summary());
  373. setupLimits(container);
  374. const auto shadow = container->add(object_ptr<Ui::SlideWrap<>>(
  375. container,
  376. object_ptr<Ui::PlainShadow>(container),
  377. st::localStorageRowPadding));
  378. createTagRow(Data::kImageCacheTag, tr::lng_local_storage_image);
  379. createTagRow(Data::kStickerCacheTag, tr::lng_local_storage_sticker);
  380. createTagRow(Data::kVoiceMessageCacheTag, tr::lng_local_storage_voice);
  381. createTagRow(Data::kVideoMessageCacheTag, tr::lng_local_storage_round);
  382. createTagRow(Data::kAnimationCacheTag, tr::lng_local_storage_animation);
  383. tracker.track(createRow(
  384. kFakeMediaCacheTag,
  385. std::move(mediaCacheTitle),
  386. tr::lng_local_storage_clear_some(),
  387. _statsBig.full));
  388. shadow->toggleOn(
  389. std::move(tracker).atLeastOneShownValue()
  390. );
  391. container->resizeToWidth(st::boxWidth);
  392. container->heightValue(
  393. ) | rpl::start_with_next([=](int height) {
  394. setDimensions(st::boxWidth, height);
  395. }, container->lifetime());
  396. }
  397. template <
  398. typename Value,
  399. typename Convert,
  400. typename Callback,
  401. typename>
  402. not_null<Ui::MediaSlider*> LocalStorageBox::createLimitsSlider(
  403. not_null<Ui::VerticalLayout*> container,
  404. int valuesCount,
  405. Convert &&convert,
  406. Value currentValue,
  407. Callback &&callback) {
  408. const auto label = container->add(
  409. object_ptr<Ui::LabelSimple>(container, st::localStorageLimitLabel),
  410. st::localStorageLimitLabelMargin);
  411. callback(label, currentValue);
  412. const auto slider = container->add(
  413. object_ptr<Ui::MediaSlider>(container, st::localStorageLimitSlider),
  414. st::localStorageLimitMargin);
  415. slider->resize(st::localStorageLimitSlider.seekSize);
  416. slider->setPseudoDiscrete(
  417. valuesCount,
  418. std::forward<Convert>(convert),
  419. currentValue,
  420. [=, callback = std::forward<Callback>(callback)](Value value) {
  421. callback(label, value);
  422. });
  423. return slider;
  424. }
  425. void LocalStorageBox::updateMediaLimit() {
  426. const auto good = [&](int64 mediaLimit) {
  427. return (_totalSizeLimit - mediaLimit >= kMinimalSizeLimit);
  428. };
  429. if (good(_mediaSizeLimit) || !_mediaSlider || !_mediaLabel) {
  430. return;
  431. }
  432. auto index = 1;
  433. while ((index < kMediaSizeLimitsCount)
  434. && (MediaSizeLimit(index) * 2 <= _totalSizeLimit)) {
  435. ++index;
  436. }
  437. --index;
  438. _mediaSizeLimit = MediaSizeLimit(index);
  439. _mediaSlider->setValue(index / float64(kMediaSizeLimitsCount - 1));
  440. updateMediaLabel();
  441. Ensures(good(_mediaSizeLimit));
  442. }
  443. void LocalStorageBox::updateTotalLimit() {
  444. const auto good = [&](int64 totalLimit) {
  445. return (totalLimit - _mediaSizeLimit >= kMinimalSizeLimit);
  446. };
  447. if (good(_totalSizeLimit) || !_totalSlider || !_totalLabel) {
  448. return;
  449. }
  450. auto index = kTotalSizeLimitsCount - 1;
  451. while ((index > 0)
  452. && (TotalSizeLimit(index - 1) >= 2 * _mediaSizeLimit)) {
  453. --index;
  454. }
  455. _totalSizeLimit = TotalSizeLimit(index);
  456. _totalSlider->setValue(index / float64(kTotalSizeLimitsCount - 1));
  457. updateTotalLabel();
  458. Ensures(good(_totalSizeLimit));
  459. }
  460. void LocalStorageBox::updateTotalLabel() {
  461. Expects(_totalLabel != nullptr);
  462. const auto text = SizeLimitText(_totalSizeLimit);
  463. _totalLabel->setText(tr::lng_local_storage_size_limit(tr::now, lt_size, text));
  464. }
  465. void LocalStorageBox::updateMediaLabel() {
  466. Expects(_mediaLabel != nullptr);
  467. const auto text = SizeLimitText(_mediaSizeLimit);
  468. _mediaLabel->setText(tr::lng_local_storage_media_limit(tr::now, lt_size, text));
  469. }
  470. void LocalStorageBox::setupLimits(not_null<Ui::VerticalLayout*> container) {
  471. container->add(
  472. object_ptr<Ui::PlainShadow>(container),
  473. st::localStorageRowPadding);
  474. _totalSlider = createLimitsSlider(
  475. container,
  476. kTotalSizeLimitsCount,
  477. TotalSizeLimit,
  478. _totalSizeLimit,
  479. [=](not_null<Ui::LabelSimple*> label, int64 limit) {
  480. _totalSizeLimit = limit;
  481. _totalLabel = label;
  482. updateTotalLabel();
  483. updateMediaLimit();
  484. limitsChanged();
  485. });
  486. _mediaSlider = createLimitsSlider(
  487. container,
  488. kMediaSizeLimitsCount,
  489. MediaSizeLimit,
  490. _mediaSizeLimit,
  491. [=](not_null<Ui::LabelSimple*> label, int64 limit) {
  492. _mediaSizeLimit = limit;
  493. _mediaLabel = label;
  494. updateMediaLabel();
  495. updateTotalLimit();
  496. limitsChanged();
  497. });
  498. createLimitsSlider(
  499. container,
  500. kTimeLimitsCount,
  501. TimeLimit,
  502. LimitToValue(_timeLimit),
  503. [=](not_null<Ui::LabelSimple*> label, size_type limit) {
  504. _timeLimit = ValueToLimit(limit);
  505. const auto text = TimeLimitText(_timeLimit);
  506. label->setText(tr::lng_local_storage_time_limit(tr::now, lt_limit, text));
  507. limitsChanged();
  508. });
  509. }
  510. void LocalStorageBox::limitsChanged() {
  511. const auto &settings = _session->local().cacheSettings();
  512. const auto &settingsBig = _session->local().cacheBigFileSettings();
  513. const auto sizeLimit = _totalSizeLimit - _mediaSizeLimit;
  514. const auto changed = (settings.totalSizeLimit != sizeLimit)
  515. || (settingsBig.totalSizeLimit != _mediaSizeLimit)
  516. || (settings.totalTimeLimit != _timeLimit)
  517. || (settingsBig.totalTimeLimit != _timeLimit);
  518. if (_limitsChanged != changed) {
  519. _limitsChanged = changed;
  520. clearButtons();
  521. if (_limitsChanged) {
  522. addButton(tr::lng_settings_save(), [=] { save(); });
  523. addButton(tr::lng_cancel(), [=] { closeBox(); });
  524. } else {
  525. addButton(tr::lng_box_ok(), [=] { closeBox(); });
  526. }
  527. }
  528. }
  529. void LocalStorageBox::save() {
  530. if (!_limitsChanged) {
  531. closeBox();
  532. return;
  533. }
  534. auto update = Storage::Cache::Database::SettingsUpdate();
  535. update.totalSizeLimit = _totalSizeLimit - _mediaSizeLimit;
  536. update.totalTimeLimit = _timeLimit;
  537. auto updateBig = Storage::Cache::Database::SettingsUpdate();
  538. updateBig.totalSizeLimit = _mediaSizeLimit;
  539. updateBig.totalTimeLimit = _timeLimit;
  540. _session->local().updateCacheSettings(update, updateBig);
  541. _session->data().cache().updateSettings(update);
  542. closeBox();
  543. }