import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../game/runtime_options.dart'; import 'game_package.dart'; import 'game_package_manifest.dart'; class StablePackageStore { const StablePackageStore({ RuntimeOptions runtimeOptions = const RuntimeOptions(), }) : _runtimeOptions = runtimeOptions; final RuntimeOptions _runtimeOptions; Future cacheRoot() async { final support = await getApplicationSupportDirectory(); final root = Directory(p.join(support.path, 'flame_lua_packages')); root.createSync(recursive: true); return root; } Future versionDirectory(String gameId, String version) async { final root = await cacheRoot(); return Directory(p.join(root.path, gameId, version)); } Future markStable(GamePackage package) async { if (package.source != GamePackageSource.file) { return; } final marker = await _markerFile(package.manifest.gameId); marker.parent.createSync(recursive: true); final previous = await stablePackage(package.manifest.gameId); final data = { 'current': package.rootPath, if (previous != null && previous.rootPath != package.rootPath) 'previous': previous.rootPath, }; final temporary = File('${marker.path}.tmp'); temporary.writeAsStringSync( const JsonEncoder.withIndent(' ').convert(data), ); if (marker.existsSync()) { marker.deleteSync(); } temporary.renameSync(marker.path); } Future stablePackage(String gameId) async { final data = await _readMarker(gameId); return _packageFromPath(data?['current']); } Future previousStablePackage(String gameId) async { final data = await _readMarker(gameId); return _packageFromPath(data?['previous']); } Future _markerFile(String gameId) async { final root = await cacheRoot(); return File(p.join(root.path, gameId, 'stable.json')); } Future?> _readMarker(String gameId) async { final marker = await _markerFile(gameId); if (!marker.existsSync()) { return null; } final source = await marker.readAsString(); if (source.trim().isEmpty) { return null; } try { final value = jsonDecode(source); if (value is Map) { return Map.from(value); } } catch (_) { return null; } return null; } GamePackage? _packageFromPath(Object? pathValue) { if (pathValue is! String || pathValue.isEmpty) { return null; } final manifestFile = File(p.join(pathValue, 'manifest.json')); if (!manifestFile.existsSync()) { return null; } return GamePackage.file( rootPath: pathValue, manifest: GamePackageManifest.fromJsonString( manifestFile.readAsStringSync(), ), runtimeLuaRoot: _runtimeOptions.runtimeLuaRoot, ); } }