mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-16 23:05:31 +02:00
perf(cache): enforce cover byte budget during foreground use
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import 'dart:io';
|
||||
|
||||
/// Trims old cached payloads to a byte budget. Repository metadata, symlinks,
|
||||
/// and recently written files (possibly active downloads) are left alone.
|
||||
Future<int> trimCacheToByteBudget(
|
||||
Directory directory, {
|
||||
required int maxBytes,
|
||||
required int targetBytes,
|
||||
Duration minimumAge = const Duration(minutes: 1),
|
||||
}) async {
|
||||
assert(targetBytes >= 0 && targetBytes <= maxBytes);
|
||||
if (!await directory.exists()) return 0;
|
||||
final files = <(File, FileStat)>[];
|
||||
var total = 0;
|
||||
await for (final entry in directory.list(
|
||||
recursive: true,
|
||||
followLinks: false,
|
||||
)) {
|
||||
if (entry is! File || entry.path.endsWith('.json')) continue;
|
||||
final stat = await entry.stat();
|
||||
if (stat.type != FileSystemEntityType.file) continue;
|
||||
files.add((entry, stat));
|
||||
total += stat.size;
|
||||
}
|
||||
if (total <= maxBytes) return 0;
|
||||
files.sort((a, b) => a.$2.modified.compareTo(b.$2.modified));
|
||||
final cutoff = DateTime.now().subtract(minimumAge);
|
||||
var deleted = 0;
|
||||
for (final (file, stat) in files) {
|
||||
if (total <= targetBytes) break;
|
||||
if (stat.modified.isAfter(cutoff)) continue;
|
||||
try {
|
||||
await file.delete();
|
||||
total -= stat.size;
|
||||
deleted++;
|
||||
} on FileSystemException {
|
||||
// A cache download/eviction may have changed the directory since listing.
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:async';
|
||||
|
||||
/// Foreground maintenance that waits between completed runs and can be paused
|
||||
/// safely even while a run is awaiting I/O.
|
||||
class PeriodicAsyncTask {
|
||||
PeriodicAsyncTask({required this.interval, required this.run, this.onError});
|
||||
final Duration interval;
|
||||
final Future<void> Function() run;
|
||||
final void Function(Object, StackTrace)? onError;
|
||||
Timer? _timer;
|
||||
bool _enabled = false;
|
||||
int _generation = 0;
|
||||
Future<void> _pending = Future<void>.value();
|
||||
|
||||
void start({required Duration delay}) {
|
||||
if (_enabled) return;
|
||||
_enabled = true;
|
||||
_schedule(delay, _generation);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_enabled = false;
|
||||
_generation++;
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
void _schedule(Duration delay, int generation) {
|
||||
_timer = Timer(delay, () async {
|
||||
final operation = _pending.then((_) async {
|
||||
if (!_enabled || generation != _generation) return;
|
||||
try {
|
||||
await run();
|
||||
} catch (error, stack) {
|
||||
onError?.call(error, stack);
|
||||
}
|
||||
});
|
||||
_pending = operation;
|
||||
try {
|
||||
await operation;
|
||||
} finally {
|
||||
if (_enabled && generation == _generation) {
|
||||
_schedule(interval, generation);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user