Files
venera/lib/foundation/cache_manager.dart
2025-04-10 17:14:05 +08:00

224 lines
5.8 KiB
Dart

import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:venera/utils/io.dart';
import 'app.dart';
class CacheManager {
static String get cachePath => '${App.cachePath}/cache';
static CacheManager? instance;
late Database _db;
int? _currentSize;
/// size in bytes
int get currentSize => _currentSize ?? 0;
int dir = 0;
int _limitSize = 2 * 1024 * 1024 * 1024;
CacheManager._create() {
Directory(cachePath).createSync(recursive: true);
_db = sqlite3.open('${App.dataPath}/cache.db');
_db.execute('''
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY NOT NULL,
dir TEXT NOT NULL,
name TEXT NOT NULL,
expires INTEGER NOT NULL,
type TEXT
)
''');
compute((path) => Directory(path).size, cachePath).then((value) {
_currentSize = value;
checkCache();
});
}
/// Get the singleton instance of CacheManager.
factory CacheManager() => instance ??= CacheManager._create();
/// set cache size limit in MB
void setLimitSize(int size) {
_limitSize = size * 1024 * 1024;
}
/// Write cache to disk.
Future<void> writeCache(String key, List<int> data,
[int duration = 7 * 24 * 60 * 60 * 1000]) async {
this.dir++;
this.dir %= 100;
var dir = this.dir;
var name = md5.convert(key.codeUnits).toString();
var file = File('$cachePath/$dir/$name');
await file.create(recursive: true);
await file.writeAsBytes(data);
var expires = DateTime.now().millisecondsSinceEpoch + duration;
_db.execute('''
INSERT OR REPLACE INTO cache (key, dir, name, expires) VALUES (?, ?, ?, ?)
''', [key, dir.toString(), name, expires]);
if (_currentSize != null) {
_currentSize = _currentSize! + data.length;
}
checkCacheIfRequired();
}
/// Find cache by key.
/// If cache is expired, it will be deleted and return null.
/// If cache is not found, it will return null.
/// If cache is found, it will return the file, and update the expires time.
Future<File?> findCache(String key) async {
var res = _db.select('''
SELECT * FROM cache
WHERE key = ?
''', [key]);
if (res.isEmpty) {
return null;
}
var row = res.first;
var dir = row[1] as String;
var name = row[2] as String;
var expires = row[3] as int;
var file = File('$cachePath/$dir/$name');
var now = DateTime.now().millisecondsSinceEpoch;
if (expires < now) {
// expired
_db.execute('''
DELETE FROM cache
WHERE key = ?
''', [key]);
if (await file.exists()) {
await file.delete();
}
return null;
}
if (await file.exists()) {
// update time
var expires = now + 7 * 24 * 60 * 60 * 1000;
_db.execute('''
UPDATE cache
SET expires = ?
WHERE key = ?
''', [expires, key]);
return file;
} else {
_db.execute('''
DELETE FROM cache
WHERE key = ?
''', [key]);
}
return null;
}
bool _isChecking = false;
/// Check cache size and delete expired cache.
/// Only check cache if current size is greater than limit size.
void checkCacheIfRequired() {
if (_currentSize != null && _currentSize! > _limitSize) {
checkCache();
}
}
/// Check cache size and delete expired cache.
/// If current size is greater than limit size,
/// delete cache until current size is less than limit size.
Future<void> checkCache() async {
if (_isChecking) {
return;
}
_isChecking = true;
var res = _db.select('''
SELECT * FROM cache
WHERE expires < ?
''', [DateTime.now().millisecondsSinceEpoch]);
for (var row in res) {
var dir = row[1] as String;
var name = row[2] as String;
var file = File('$cachePath/$dir/$name');
if (await file.exists()) {
var size = await file.length();
_currentSize = _currentSize! - size;
await file.delete();
}
}
_db.execute('''
DELETE FROM cache
WHERE expires < ?
''', [DateTime.now().millisecondsSinceEpoch]);
while (_currentSize != null && _currentSize! > _limitSize) {
var res = _db.select('''
SELECT * FROM cache
ORDER BY expires ASC
limit 10
''');
for (var row in res) {
var key = row[0] as String;
var dir = row[1] as String;
var name = row[2] as String;
var file = File('$cachePath/$dir/$name');
if (await file.exists()) {
var size = await file.length();
await file.delete();
_db.execute('''
DELETE FROM cache
WHERE key = ?
''', [key]);
_currentSize = _currentSize! - size;
if (_currentSize! <= _limitSize) {
break;
}
} else {
_db.execute('''
DELETE FROM cache
WHERE key = ?
''', [key]);
}
}
}
_isChecking = false;
}
/// Delete cache by key.
Future<void> delete(String key) async {
var res = _db.select('''
SELECT * FROM cache
WHERE key = ?
''', [key]);
if (res.isEmpty) {
return;
}
var row = res.first;
var dir = row[1] as String;
var name = row[2] as String;
var file = File('$cachePath/$dir/$name');
var fileSize = 0;
if (await file.exists()) {
fileSize = await file.length();
await file.delete();
}
_db.execute('''
DELETE FROM cache
WHERE key = ?
''', [key]);
if (_currentSize != null) {
_currentSize = _currentSize! - fileSize;
}
}
/// Delete all cache.
Future<void> clear() async {
await Directory(cachePath).delete(recursive: true);
Directory(cachePath).createSync(recursive: true);
_db.execute('''
DELETE FROM cache
''');
_currentSize = 0;
}
}