From 8fffb9230a53e57d1b0d5c79ba970d7fd6244b36 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:53:34 +0700 Subject: [PATCH] refactor(cache): move directory scanning and clearing into CacheMaintenanceService The cache management page walked and deleted directories itself. Move _scanDirectory and _clearDirectoryContents verbatim into CacheMaintenanceService.scanDirectory and clearDirectoryContents in lib/services/cache_maintenance_service.dart, with _DirectoryStats becoming the public DirectoryStats it returns. The page calls the service at the same points with the same arguments, so scan concurrency, the 24-entity delete chunks, the directory re-creation after clearing and every action key are unchanged. --- .../settings/cache_management_page.dart | 99 ++++--------------- lib/services/cache_maintenance_service.dart | 65 ++++++++++++ 2 files changed, 84 insertions(+), 80 deletions(-) create mode 100644 lib/services/cache_maintenance_service.dart diff --git a/lib/screens/settings/cache_management_page.dart b/lib/screens/settings/cache_management_page.dart index 2b1fd3e5..2666e048 100644 --- a/lib/screens/settings/cache_management_page.dart +++ b/lib/screens/settings/cache_management_page.dart @@ -13,6 +13,7 @@ import 'package:spotiflac_android/providers/download_queue_provider.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/services/audio_analysis_cache.dart'; +import 'package:spotiflac_android/services/cache_maintenance_service.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; import 'package:spotiflac_android/providers/player_motion_artwork_provider.dart'; import 'package:spotiflac_android/services/motion_artwork_store.dart'; @@ -76,7 +77,7 @@ class _CacheManagementPageState extends ConsumerState { final appSupportDirFuture = getApplicationSupportDirectory(); final coverStatsFuture = CoverCacheManager.getStats(); final motionStatsFuture = MotionArtworkStore.defaultDirectory().then( - _scanDirectory, + CacheMaintenanceService.scanDirectory, ); final prefsFuture = SharedPreferences.getInstance(); final trackCacheEntriesFuture = _getTrackCacheSizeSafe(); @@ -87,16 +88,18 @@ class _CacheManagementPageState extends ConsumerState { final tempPath = p.normalize(tempDir.path); final tempIsSameAsAppCache = appCachePath == tempPath; - final appCacheStatsFuture = _scanDirectory(Directory(appCachePath)); + final appCacheStatsFuture = CacheMaintenanceService.scanDirectory( + Directory(appCachePath), + ); final tempStatsFuture = tempIsSameAsAppCache - ? Future<_DirectoryStats?>.value(null) - : _scanDirectory(Directory(tempPath)); + ? Future.value(null) + : CacheMaintenanceService.scanDirectory(Directory(tempPath)); final appSupportDir = await appSupportDirFuture; - final libraryCoverStatsFuture = _scanDirectory( + final libraryCoverStatsFuture = CacheMaintenanceService.scanDirectory( Directory('${appSupportDir.path}/library_covers'), ); - final audioAnalysisStatsFuture = _scanDirectory( + final audioAnalysisStatsFuture = CacheMaintenanceService.scanDirectory( AudioAnalysisDiskCache.directoryIn(appSupportDir.path), ); @@ -127,29 +130,6 @@ class _CacheManagementPageState extends ConsumerState { ); } - Future<_DirectoryStats> _scanDirectory(Directory directory) async { - if (!await directory.exists()) { - return const _DirectoryStats(fileCount: 0, totalSizeBytes: 0); - } - - var fileCount = 0; - var totalSize = 0; - - try { - await for (final entity in directory.list( - recursive: true, - followLinks: false, - )) { - if (entity is File) { - fileCount++; - totalSize += await entity.length(); - } - } - } catch (_) {} - - return _DirectoryStats(fileCount: fileCount, totalSizeBytes: totalSize); - } - Future _getTrackCacheSizeSafe() async { try { return await PlatformBridge.getTrackCacheSize(); @@ -158,46 +138,15 @@ class _CacheManagementPageState extends ConsumerState { } } - Future _clearDirectoryContents(String path) async { - final directory = Directory(path); - if (!await directory.exists()) return; - - try { - final entities = []; - await for (final entity in directory.list(followLinks: false)) { - entities.add(entity); - } - - const deleteChunkSize = 24; - for (var i = 0; i < entities.length; i += deleteChunkSize) { - final end = (i + deleteChunkSize < entities.length) - ? i + deleteChunkSize - : entities.length; - final chunk = entities.sublist(i, end); - await Future.wait( - chunk.map((entity) async { - try { - await entity.delete(recursive: true); - } catch (_) {} - }), - ); - } - } catch (_) {} - - try { - await directory.create(recursive: true); - } catch (_) {} - } - Future _clearAppCache() async { await DownloadedEmbeddedCoverResolver.clearPersistentCache(); final cacheDir = await getApplicationCacheDirectory(); - await _clearDirectoryContents(cacheDir.path); + await CacheMaintenanceService.clearDirectoryContents(cacheDir.path); } Future _clearTempCache() async { final tempDir = await getTemporaryDirectory(); - await _clearDirectoryContents(tempDir.path); + await CacheMaintenanceService.clearDirectoryContents(tempDir.path); } Future _clearCoverCache() async { @@ -212,13 +161,13 @@ class _CacheManagementPageState extends ConsumerState { Future _clearLibraryCoverCache() async { final appSupportDir = await getApplicationSupportDirectory(); final libraryCoverDir = Directory('${appSupportDir.path}/library_covers'); - await _clearDirectoryContents(libraryCoverDir.path); + await CacheMaintenanceService.clearDirectoryContents(libraryCoverDir.path); } Future _clearAudioAnalysisCache() async { final appSupportDir = await getApplicationSupportDirectory(); final analysisDir = AudioAnalysisDiskCache.directoryIn(appSupportDir.path); - await _clearDirectoryContents(analysisDir.path); + await CacheMaintenanceService.clearDirectoryContents(analysisDir.path); } Future _clearExploreCache() async { @@ -364,7 +313,7 @@ class _CacheManagementPageState extends ConsumerState { }); } - String _formatDirectorySize(_DirectoryStats stats) { + String _formatDirectorySize(DirectoryStats stats) { if (stats.fileCount == 0 || stats.totalSizeBytes == 0) { return context.l10n.cacheNoData; } @@ -720,14 +669,14 @@ class _CacheManagementPageState extends ConsumerState { class _CacheOverview { final String appCachePath; - final _DirectoryStats appCacheStats; + final DirectoryStats appCacheStats; final String? tempPath; - final _DirectoryStats? tempStats; + final DirectoryStats? tempStats; final bool tempIsSameAsAppCache; final CacheStats coverStats; - final _DirectoryStats motionStats; - final _DirectoryStats libraryCoverStats; - final _DirectoryStats audioAnalysisStats; + final DirectoryStats motionStats; + final DirectoryStats libraryCoverStats; + final DirectoryStats audioAnalysisStats; final int exploreCacheBytes; final bool hasExploreCache; final int trackCacheEntries; @@ -755,13 +704,3 @@ class _CacheOverview { exploreCacheBytes; } } - -class _DirectoryStats { - final int fileCount; - final int totalSizeBytes; - - const _DirectoryStats({ - required this.fileCount, - required this.totalSizeBytes, - }); -} diff --git a/lib/services/cache_maintenance_service.dart b/lib/services/cache_maintenance_service.dart new file mode 100644 index 00000000..6bb7924b --- /dev/null +++ b/lib/services/cache_maintenance_service.dart @@ -0,0 +1,65 @@ +import 'dart:io'; + +class DirectoryStats { + final int fileCount; + final int totalSizeBytes; + + const DirectoryStats({required this.fileCount, required this.totalSizeBytes}); +} + +/// Directory sizing and clearing for the cache management page. +abstract final class CacheMaintenanceService { + static Future scanDirectory(Directory directory) async { + if (!await directory.exists()) { + return const DirectoryStats(fileCount: 0, totalSizeBytes: 0); + } + + var fileCount = 0; + var totalSize = 0; + + try { + await for (final entity in directory.list( + recursive: true, + followLinks: false, + )) { + if (entity is File) { + fileCount++; + totalSize += await entity.length(); + } + } + } catch (_) {} + + return DirectoryStats(fileCount: fileCount, totalSizeBytes: totalSize); + } + + static Future clearDirectoryContents(String path) async { + final directory = Directory(path); + if (!await directory.exists()) return; + + try { + final entities = []; + await for (final entity in directory.list(followLinks: false)) { + entities.add(entity); + } + + const deleteChunkSize = 24; + for (var i = 0; i < entities.length; i += deleteChunkSize) { + final end = (i + deleteChunkSize < entities.length) + ? i + deleteChunkSize + : entities.length; + final chunk = entities.sublist(i, end); + await Future.wait( + chunk.map((entity) async { + try { + await entity.delete(recursive: true); + } catch (_) {} + }), + ); + } + } catch (_) {} + + try { + await directory.create(recursive: true); + } catch (_) {} + } +}