From 44a23cd532cc5e8aac448733d3bd91fd069a42d0 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 27 May 2024 10:52:07 +0800 Subject: [PATCH] Show cached size in settings page --- lib/l10n/app_en.arb | 8 ++- lib/l10n/app_zh.arb | 6 +- lib/platform/image_cache_io.dart | 70 ++++++++++++++++++++++ lib/platform/image_cache_web.dart | 5 ++ lib/settings.dart | 98 +++++++++++++++++++------------ 5 files changed, 148 insertions(+), 39 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 40b9c32..0c98a87 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -26,7 +26,7 @@ "showNsfw": "Show NSFW image by default", "read": "Read", "download": "Download", - "colon": ":", + "colon": ": ", "copyImage": "Copy image to clipboard", "copyImgUrl": "Copy image URL to clipboard", "retry": "Retry", @@ -196,5 +196,9 @@ "refresh": "Refresh", "originalImg": "Original image", "overwriteDefaultConfig": "Overwrite default config", - "enableImageCache": "Cache images to disk." + "enableImageCache": "Cache images to disk.", + "cachedFileSize": "Cached file size", + "update": "Update", + "updateFileSize": "Update file size", + "clearCaches": "Clear caches" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index bb4c138..23690a7 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -196,5 +196,9 @@ "refresh": "刷新", "originalImg": "原图", "overwriteDefaultConfig": "覆盖默认设置", - "enableImageCache": "将图片缓存到本地硬盘。" + "enableImageCache": "将图片缓存到本地硬盘。", + "cachedFileSize": "已缓存文件大小", + "update": "更新", + "updateFileSize": "更新文件大小", + "clearCaches": "清除缓存" } diff --git a/lib/platform/image_cache_io.dart b/lib/platform/image_cache_io.dart index 5a7d2c4..3aa12a1 100644 --- a/lib/platform/image_cache_io.dart +++ b/lib/platform/image_cache_io.dart @@ -31,6 +31,8 @@ class ImageCaches { final Set _existingTable = {}; bool _inited = false; static const version = 1; + int _size = 0; + int get size => _size; ImageCaches(); Future _desktopFilePath() async { final String? exe = await platformPath.getCurrentExe(); @@ -123,11 +125,42 @@ class ImageCaches { await _db!.execute("VACUUM;"); } + Future _removeUnexist() async { + List needDeleted = []; + int offset = 0; + late List> records; + do { + records = await _db!.query("images", + columns: ["url", "path"], limit: 100, offset: offset); + for (final record in records) { + final url = record["url"] as String; + var p = record["path"] as String; + if (_exeDir != null && path.isRelative(p)) { + p = path.join(_exeDir!, p); + } + final f = _fs.file(p); + try { + if (!await f.exists()) { + needDeleted.add(url); + } + } catch (e) { + _log.warning("Failed to check $p is exists or not. Url: $url"); + } + } + offset += records.length; + } while (records.isNotEmpty); + for (final url in needDeleted) { + await _db!.delete("images", where: "url = ?", whereArgs: [url]); + } + if (needDeleted.isNotEmpty) await _optimize(); + } + Future init() async { sqfliteFfiInit(); _db = await databaseFactoryFfi.openDatabase(await _filePath); await _createDir(); if (!(await _checkDatabase())) await _createTable(); + await updateSize(); _inited = true; } @@ -186,8 +219,45 @@ class ImageCaches { if (_exeDir != null) { p = path.relative(p, from: _exeDir!); } + final exes = await _db!.query("images", where: 'url = ?', whereArgs: [uri]); await _db!.rawInsert( "INSERT OR REPLACE INTO images VALUES (?, ?, ?, ?, ?, ?);", [uri, p, lastUsed, header, realUri, data.length]); + if (exes.isEmpty) { + _size += data.length; + } else { + final originalSize = (exes[0]["size"] as int?) ?? 0; + _size += (data.length - originalSize); + } + } + + Future updateSize({bool clear = false}) async { + if (clear) await _removeUnexist(); + final re = await _db!.rawQuery("SELECT SUM(size) AS sizes FROM images;"); + _size = re[0]["sizes"] as int; + } + + Future clear() async { + int offset = 0; + late List> records; + do { + records = await _db! + .query("images", columns: ["path"], limit: 100, offset: offset); + for (final record in records) { + var p = record["path"] as String; + if (_exeDir != null && path.isRelative(p)) { + p = path.join(_exeDir!, p); + } + final f = _fs.file(p); + try { + await f.delete(); + } catch (e) { + _log.warning("Failed to delete $p"); + } + } + offset += records.length; + } while (records.isNotEmpty); + await _db!.delete("images"); + _size = 0; } } diff --git a/lib/platform/image_cache_web.dart b/lib/platform/image_cache_web.dart index 0b75c0c..b1ab898 100644 --- a/lib/platform/image_cache_web.dart +++ b/lib/platform/image_cache_web.dart @@ -6,6 +6,8 @@ import 'package:web/web.dart'; class ImageCaches { Cache? cache; ImageCaches(); + int _size = 0; + int get size => _size; Future init() async { cache = await window.caches.open("image_caches").toDart; } @@ -42,4 +44,7 @@ class ImageCaches { final res = Response(data.toJS, opts); await cache!.put(uri.toJS, res).toDart; } + + Future updateSize({bool clear = false}) async {} + Future clear() async {} } diff --git a/lib/settings.dart b/lib/settings.dart index d2a8af6..22b3661 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -5,6 +5,7 @@ import 'package:logging/logging.dart'; import 'globals.dart'; import 'main.dart'; import 'utils.dart'; +import 'utils/filesize.dart'; final _log = Logger("SettingsPage"); @@ -200,10 +201,57 @@ class _SettingsPage extends State with ThemeModeWidget { return re; } + Widget _buildCache(BuildContext context) { + final i18n = AppLocalizations.of(context)!; + final text = SelectableText( + "${i18n.cachedFileSize}${i18n.colon}${getFileSize(imageCaches.size)}"); + final button = ElevatedButton( + onPressed: () { + imageCaches.updateSize(clear: true).then((_) { + setState(() {}); + }).onError((e, _) { + _log.warning("Failed to update image cache size: $e"); + return null; + }); + }, + child: Text(i18n.updateFileSize)); + final cButton = Container( + padding: const EdgeInsets.only(left: 8), + child: ElevatedButton( + onPressed: () { + imageCaches.clear().then((_) { + setState(() {}); + }).onError((e, _) { + _log.warning("Failed to clear image cache: $e"); + return null; + }); + }, + child: Text(i18n.clearCaches))); + final maxWidth = MediaQuery.of(context).size.width; + final useTwoLine = maxWidth <= 500 ? true : false; + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + CheckboxMenuButton( + value: _enableImageCache, + onChanged: (bool? value) { + if (value != null) { + setState(() { + _enableImageCache = value; + }); + } + }, + child: Text(i18n.enableImageCache), + ), + Container( + padding: const EdgeInsets.only(left: 6, top: 4), + child: useTwoLine ? text : Row(children: [text, button, cButton])), + useTwoLine ? Row(children: [button, cButton]) : Container(), + ]); + } + @override Widget build(BuildContext context) { - setCurrentTitle(AppLocalizations.of(context)!.settings, - Theme.of(context).primaryColor.value); + final i18n = AppLocalizations.of(context)!; + setCurrentTitle(i18n.settings, Theme.of(context).primaryColor.value); return Scaffold( appBar: AppBar( leading: IconButton( @@ -213,7 +261,7 @@ class _SettingsPage extends State with ThemeModeWidget { }, icon: const Icon(Icons.arrow_back), ), - title: Text(AppLocalizations.of(context)!.settings), + title: Text(i18n.settings), actions: [ buildThemeModeIcon(context), buildMoreVertSettingsButon(context), @@ -245,14 +293,12 @@ class _SettingsPage extends State with ThemeModeWidget { MainApp.of(context).changeLang(_lang); } }, - label: - Text(AppLocalizations.of(context)!.lang), + label: Text(i18n.lang), dropdownMenuEntries: Lang.values .map((e) => DropdownMenuEntry( value: e, label: e == Lang.system - ? AppLocalizations.of(context)! - .systemLang + ? i18n.systemLang : e.langName)) .toList(), leadingIcon: const Icon(Icons.language), @@ -269,8 +315,7 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text(AppLocalizations.of(context)! - .useTitleJpn), + child: Text(i18n.useTitleJpn), )), Container( padding: const EdgeInsets.symmetric(vertical: 8), @@ -283,8 +328,7 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text( - AppLocalizations.of(context)!.showNsfw), + child: Text(i18n.showNsfw), ), ), Container( @@ -298,8 +342,7 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text( - AppLocalizations.of(context)!.displayAd), + child: Text(i18n.displayAd), ), ), Container( @@ -313,8 +356,7 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text(AppLocalizations.of(context)! - .showTranslatedTag), + child: Text(i18n.showTranslatedTag), ), ), isAndroid || isWindows @@ -330,8 +372,7 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text(AppLocalizations.of(context)! - .preventScreenCapture), + child: Text(i18n.preventScreenCapture), ), ) : Container(), @@ -346,24 +387,12 @@ class _SettingsPage extends State with ThemeModeWidget { }); } }, - child: Text(AppLocalizations.of(context)! - .dlUseAvgSpeed), + child: Text(i18n.dlUseAvgSpeed), ), ), Container( padding: const EdgeInsets.symmetric(vertical: 8), - child: CheckboxMenuButton( - value: _enableImageCache, - onChanged: (bool? value) { - if (value != null) { - setState(() { - _enableImageCache = value; - }); - } - }, - child: Text(AppLocalizations.of(context)! - .enableImageCache), - ), + child: _buildCache(context), ), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -375,9 +404,7 @@ class _SettingsPage extends State with ThemeModeWidget { onPressed: () { reset(context); }, - child: Text( - AppLocalizations.of(context)! - .reset))), + child: Text(i18n.reset))), Padding( padding: const EdgeInsets.symmetric( horizontal: 8), @@ -385,8 +412,7 @@ class _SettingsPage extends State with ThemeModeWidget { onPressed: () { save(); }, - child: Text( - AppLocalizations.of(context)!.save), + child: Text(i18n.save), )) ], ),