Skip to content

commit 8203c44

abduznik edited this page May 23, 2026 · 1 revision

fix(storage): improve emudeck robustness and add background file sanity check

Commit: 8203c44e831c7b5226000e810ad86a79d11b837d

Author: abduznik

Date: 2026-04-28

Why: Fixes a bug or regression in the existing codebase.

Files Changed

lib/app.dart                                       |  4 ++
 .../linux_strategies/emudeck_strategy.dart         | 13 +++++
 lib/core/storage/file_sanity_service.dart          | 66 ++++++++++++++++++++++
 3 files changed, 83 insertions(+)
  • lib/app.dart
  • lib/core/emulator/linux_strategies/emudeck_strategy.dart
  • lib/core/storage/file_sanity_service.dart

Diff

diff --git a/lib/app.dart b/lib/app.dart
index 6b1a6dd..44a3e7f 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -12,6 +12,7 @@ import 'ui/screens/download_screen.dart';
 import 'ui/screens/settings_screen.dart';
 import 'ui/screens/onboarding_screen.dart';
 import 'providers/ui_provider.dart';
+import 'core/storage/file_sanity_service.dart';
 
 class CustomScrollBehavior extends MaterialScrollBehavior {
   @override
@@ -74,6 +75,9 @@ class _FreegosyAppState extends ConsumerState<FreegosyApp> {
       }
     });
 
+    // Keep the file sanity service alive and running in the background
+    ref.watch(fileSanityServiceProvider);
+
     final currentIndex = ref.watch(currentTabIndexProvider);
 
     return ExcludeSemantics(
diff --git a/lib/core/emulator/linux_strategies/emudeck_strategy.dart b/lib/core/emulator/linux_strategies/emudeck_strategy.dart
index 1940bf6..ff0d1cb 100644
--- a/lib/core/emulator/linux_strategies/emudeck_strategy.dart
+++ b/lib/core/emulator/linux_strategies/emudeck_strategy.dart
@@ -13,6 +13,13 @@ class EmuDeckStrategy extends LinuxEnvironmentStrategy {
   @override
   String getRomsRoot(String home, String? customPath, String? emudeckRoot) {
     if (emudeckRoot != null) {
+      // Robustness: if user pointed directly to 'Emulation' or 'Emulation/roms', handle it
+      if (p.basename(emudeckRoot).toLowerCase() == 'roms' && p.basename(p.dirname(emudeckRoot)).toLowerCase() == 'emulation') {
+        return customPath ?? emudeckRoot;
+      }
+      if (p.basename(emudeckRoot).toLowerCase() == 'emulation') {
+        return customPath ?? p.join(emudeckRoot, 'roms');
+      }
       return customPath ?? p.join(emudeckRoot, 'Emulation/roms');
     }
     return customPath ?? p.join(home, 'ROMs');
@@ -21,6 +28,12 @@ class EmuDeckStrategy extends LinuxEnvironmentStrategy {
   @override
   String getEmulatorsRoot(String home, String? customPath, String? emudeckRoot) {
     if (emudeckRoot != null) {
+      if (p.basename(emudeckRoot).toLowerCase() == 'tools' && p.basename(p.dirname(emudeckRoot)).toLowerCase() == 'emulation') {
+        return customPath ?? emudeckRoot;
+      }
+      if (p.basename(emudeckRoot).toLowerCase() == 'emulation') {
+        return customPath ?? p.join(emudeckRoot, 'tools');
+      }
       return customPath ?? p.join(emudeckRoot, 'Emulation/tools');
     }
     return customPath ?? p.join(home, 'Emulators');
diff --git a/lib/core/storage/file_sanity_service.dart b/lib/core/storage/file_sanity_service.dart
new file mode 100644
index 0000000..6b7efd6
--- /dev/null
+++ b/lib/core/storage/file_sanity_service.dart
@@ -0,0 +1,66 @@
+import 'dart:async';
+import 'dart:io' as io;
+import 'package:flutter/foundation.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:freegosy/core/storage/directory_service.dart';
+import 'package:freegosy/core/storage/rom_mapping_service.dart';
+import 'package:freegosy/core/storage/download_cache_service.dart';
+import 'package:freegosy/providers/romm_provider.dart';
+
+class FileSanityService {
+  final RomMappingService _mappingService;
+  final DownloadCacheService _cacheService;
+  Timer? _timer;
+
+  FileSanityService(this._mappingService, this._cacheService);
+
+  void start() {
+    _timer?.cancel();
+    // Run every 10 minutes
+    _timer = Timer.periodic(const Duration(minutes: 10), (_) => pruneStaleEntries());
+    // Also run once immediately
+    pruneStaleEntries();
+  }
+
+  void stop() {
+    _timer?.cancel();
+  }
+
+  Future<void> pruneStaleEntries() async {
+    debugPrint('[Sanity] Checking for stale ROM mappings...');
+    final mappings = _mappingService.getMappings();
+    int removedCount = 0;
+
+    for (final entry in mappings.entries) {
+      final path = entry.key;
+      final file = io.File(path);
+      
+      if (!file.existsSync()) {
+        debugPrint('[Sanity] File no longer exists, removing mapping: $path');
+        await _mappingService.removeMapping(path);
+        _cacheService.removeFile(io.File(path).path); // Best effort removal from cache
+        removedCount++;
+      }
+    }
+
+    if (removedCount > 0) {
+      debugPrint('[Sanity] Pruned $removedCount stale entries.');
+    }
+  }
+}
+
+final fileSanityServiceProvider = Provider<FileSanityService?>((ref) {
+  final dirService = ref.watch(directoryServiceProvider).asData?.value;
+  final mappingServiceAsync = ref.watch(romMappingServiceProvider);
+  final cacheService = ref.watch(downloadCacheServiceProvider);
+
+  if (!mappingServiceAsync.hasValue) {
+     return null;
+  }
+
+  final service = FileSanityService(mappingServiceAsync.value!, cacheService);
+  service.start();
+  
+  ref.onDispose(() => service.stop());
+  return service;
+});

Clone this wiki locally