Show cached size in settings page

This commit is contained in:
2024-05-27 10:52:07 +08:00
parent b19265ef32
commit 44a23cd532
5 changed files with 148 additions and 39 deletions

View File

@@ -26,7 +26,7 @@
"showNsfw": "Show NSFW image by default", "showNsfw": "Show NSFW image by default",
"read": "Read", "read": "Read",
"download": "Download", "download": "Download",
"colon": ":", "colon": ": ",
"copyImage": "Copy image to clipboard", "copyImage": "Copy image to clipboard",
"copyImgUrl": "Copy image URL to clipboard", "copyImgUrl": "Copy image URL to clipboard",
"retry": "Retry", "retry": "Retry",
@@ -196,5 +196,9 @@
"refresh": "Refresh", "refresh": "Refresh",
"originalImg": "Original image", "originalImg": "Original image",
"overwriteDefaultConfig": "Overwrite default config", "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"
} }

View File

@@ -196,5 +196,9 @@
"refresh": "刷新", "refresh": "刷新",
"originalImg": "原图", "originalImg": "原图",
"overwriteDefaultConfig": "覆盖默认设置", "overwriteDefaultConfig": "覆盖默认设置",
"enableImageCache": "将图片缓存到本地硬盘。" "enableImageCache": "将图片缓存到本地硬盘。",
"cachedFileSize": "已缓存文件大小",
"update": "更新",
"updateFileSize": "更新文件大小",
"clearCaches": "清除缓存"
} }

View File

@@ -31,6 +31,8 @@ class ImageCaches {
final Set<String> _existingTable = {}; final Set<String> _existingTable = {};
bool _inited = false; bool _inited = false;
static const version = 1; static const version = 1;
int _size = 0;
int get size => _size;
ImageCaches(); ImageCaches();
Future<String?> _desktopFilePath() async { Future<String?> _desktopFilePath() async {
final String? exe = await platformPath.getCurrentExe(); final String? exe = await platformPath.getCurrentExe();
@@ -123,11 +125,42 @@ class ImageCaches {
await _db!.execute("VACUUM;"); await _db!.execute("VACUUM;");
} }
Future<void> _removeUnexist() async {
List<String> needDeleted = [];
int offset = 0;
late List<Map<String, Object?>> 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<void> init() async { Future<void> init() async {
sqfliteFfiInit(); sqfliteFfiInit();
_db = await databaseFactoryFfi.openDatabase(await _filePath); _db = await databaseFactoryFfi.openDatabase(await _filePath);
await _createDir(); await _createDir();
if (!(await _checkDatabase())) await _createTable(); if (!(await _checkDatabase())) await _createTable();
await updateSize();
_inited = true; _inited = true;
} }
@@ -186,8 +219,45 @@ class ImageCaches {
if (_exeDir != null) { if (_exeDir != null) {
p = path.relative(p, from: _exeDir!); p = path.relative(p, from: _exeDir!);
} }
final exes = await _db!.query("images", where: 'url = ?', whereArgs: [uri]);
await _db!.rawInsert( await _db!.rawInsert(
"INSERT OR REPLACE INTO images VALUES (?, ?, ?, ?, ?, ?);", "INSERT OR REPLACE INTO images VALUES (?, ?, ?, ?, ?, ?);",
[uri, p, lastUsed, header, realUri, data.length]); [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<void> 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<void> clear() async {
int offset = 0;
late List<Map<String, Object?>> 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;
} }
} }

View File

@@ -6,6 +6,8 @@ import 'package:web/web.dart';
class ImageCaches { class ImageCaches {
Cache? cache; Cache? cache;
ImageCaches(); ImageCaches();
int _size = 0;
int get size => _size;
Future<void> init() async { Future<void> init() async {
cache = await window.caches.open("image_caches").toDart; cache = await window.caches.open("image_caches").toDart;
} }
@@ -42,4 +44,7 @@ class ImageCaches {
final res = Response(data.toJS, opts); final res = Response(data.toJS, opts);
await cache!.put(uri.toJS, res).toDart; await cache!.put(uri.toJS, res).toDart;
} }
Future<void> updateSize({bool clear = false}) async {}
Future<void> clear() async {}
} }

View File

@@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import 'globals.dart'; import 'globals.dart';
import 'main.dart'; import 'main.dart';
import 'utils.dart'; import 'utils.dart';
import 'utils/filesize.dart';
final _log = Logger("SettingsPage"); final _log = Logger("SettingsPage");
@@ -200,10 +201,57 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
return re; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
setCurrentTitle(AppLocalizations.of(context)!.settings, final i18n = AppLocalizations.of(context)!;
Theme.of(context).primaryColor.value); setCurrentTitle(i18n.settings, Theme.of(context).primaryColor.value);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
@@ -213,7 +261,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}, },
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
title: Text(AppLocalizations.of(context)!.settings), title: Text(i18n.settings),
actions: [ actions: [
buildThemeModeIcon(context), buildThemeModeIcon(context),
buildMoreVertSettingsButon(context), buildMoreVertSettingsButon(context),
@@ -245,14 +293,12 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
MainApp.of(context).changeLang(_lang); MainApp.of(context).changeLang(_lang);
} }
}, },
label: label: Text(i18n.lang),
Text(AppLocalizations.of(context)!.lang),
dropdownMenuEntries: Lang.values dropdownMenuEntries: Lang.values
.map((e) => DropdownMenuEntry( .map((e) => DropdownMenuEntry(
value: e, value: e,
label: e == Lang.system label: e == Lang.system
? AppLocalizations.of(context)! ? i18n.systemLang
.systemLang
: e.langName)) : e.langName))
.toList(), .toList(),
leadingIcon: const Icon(Icons.language), leadingIcon: const Icon(Icons.language),
@@ -269,8 +315,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text(AppLocalizations.of(context)! child: Text(i18n.useTitleJpn),
.useTitleJpn),
)), )),
Container( Container(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@@ -283,8 +328,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text( child: Text(i18n.showNsfw),
AppLocalizations.of(context)!.showNsfw),
), ),
), ),
Container( Container(
@@ -298,8 +342,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text( child: Text(i18n.displayAd),
AppLocalizations.of(context)!.displayAd),
), ),
), ),
Container( Container(
@@ -313,8 +356,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text(AppLocalizations.of(context)! child: Text(i18n.showTranslatedTag),
.showTranslatedTag),
), ),
), ),
isAndroid || isWindows isAndroid || isWindows
@@ -330,8 +372,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text(AppLocalizations.of(context)! child: Text(i18n.preventScreenCapture),
.preventScreenCapture),
), ),
) )
: Container(), : Container(),
@@ -346,24 +387,12 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
}); });
} }
}, },
child: Text(AppLocalizations.of(context)! child: Text(i18n.dlUseAvgSpeed),
.dlUseAvgSpeed),
), ),
), ),
Container( Container(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: CheckboxMenuButton( child: _buildCache(context),
value: _enableImageCache,
onChanged: (bool? value) {
if (value != null) {
setState(() {
_enableImageCache = value;
});
}
},
child: Text(AppLocalizations.of(context)!
.enableImageCache),
),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -375,9 +404,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
onPressed: () { onPressed: () {
reset(context); reset(context);
}, },
child: Text( child: Text(i18n.reset))),
AppLocalizations.of(context)!
.reset))),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8), horizontal: 8),
@@ -385,8 +412,7 @@ class _SettingsPage extends State<SettingsPage> with ThemeModeWidget {
onPressed: () { onPressed: () {
save(); save();
}, },
child: Text( child: Text(i18n.save),
AppLocalizations.of(context)!.save),
)) ))
], ],
), ),