mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-17 08:30:33 +02:00
fix(android): sanitize restored installation state
This commit is contained in:
@@ -251,6 +251,16 @@ class AppStateDatabase {
|
||||
});
|
||||
}
|
||||
|
||||
/// A pending queue is tied to the installation's output grants and worker
|
||||
/// lifecycle. It must not resume after Android restores data into a newly
|
||||
/// installed package.
|
||||
Future<void> clearPendingQueueAfterInstallationRestore() async {
|
||||
await replacePendingDownloadQueueRows(const []);
|
||||
final prefs = await _prefs;
|
||||
await prefs.remove(_legacyQueueKey);
|
||||
await prefs.setBool(_queueMigrationKey, true);
|
||||
}
|
||||
|
||||
Future<void> applyPendingDownloadQueueChanges({
|
||||
required List<Map<String, dynamic>> upserts,
|
||||
required List<String> deletedIds,
|
||||
|
||||
@@ -31,6 +31,41 @@ class IosPickedDirectory {
|
||||
const IosPickedDirectory({required this.path, required this.bookmark});
|
||||
}
|
||||
|
||||
class InstallationState {
|
||||
final bool markerExisted;
|
||||
final bool markerCreated;
|
||||
final bool freshPackageInstall;
|
||||
|
||||
const InstallationState({
|
||||
required this.markerExisted,
|
||||
required this.markerCreated,
|
||||
required this.freshPackageInstall,
|
||||
});
|
||||
|
||||
const InstallationState.unsupported()
|
||||
: markerExisted = true,
|
||||
markerCreated = true,
|
||||
freshPackageInstall = false;
|
||||
|
||||
factory InstallationState.fromMap(Map<String, dynamic> map) {
|
||||
return InstallationState(
|
||||
markerExisted: map['marker_existed'] == true,
|
||||
markerCreated: map['marker_created'] == true,
|
||||
freshPackageInstall: map['fresh_package_install'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldResetRestoredInstallation({
|
||||
required bool hasPersistedSettings,
|
||||
required InstallationState installState,
|
||||
}) {
|
||||
return hasPersistedSettings &&
|
||||
!installState.markerExisted &&
|
||||
installState.markerCreated &&
|
||||
installState.freshPackageInstall;
|
||||
}
|
||||
|
||||
class _BridgeCacheEntry {
|
||||
final Map<String, dynamic> value;
|
||||
final DateTime expiresAt;
|
||||
@@ -137,6 +172,12 @@ class PlatformBridge {
|
||||
return _decodeRequiredMapResult(result, method);
|
||||
}
|
||||
|
||||
static Future<InstallationState> ensureInstallMarker() async {
|
||||
if (!Platform.isAndroid) return const InstallationState.unsupported();
|
||||
final result = await _invokeMap('ensureInstallMarker');
|
||||
return InstallationState.fromMap(result);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> _cachedInvoke(
|
||||
String cacheKey,
|
||||
Map<String, _BridgeCacheEntry> cache,
|
||||
@@ -578,16 +619,23 @@ class PlatformBridge {
|
||||
/// so a bridge problem never raises a false "access lost" alarm.
|
||||
static Future<bool> isSafTreeAccessible(String treeUri) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod('isSafTreeAccessible', {
|
||||
'tree_uri': treeUri,
|
||||
});
|
||||
return result as bool? ?? true;
|
||||
return await validateSafTreeAccess(treeUri);
|
||||
} catch (e) {
|
||||
_log.w('Failed to check SAF tree accessibility: $e');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Strict SAF validation for startup and download preflight. Unlike
|
||||
/// [isSafTreeAccessible], channel failures are surfaced to the caller so a
|
||||
/// write cannot proceed on an unverified destination.
|
||||
static Future<bool> validateSafTreeAccess(String treeUri) async {
|
||||
final result = await _channel.invokeMethod('isSafTreeAccessible', {
|
||||
'tree_uri': treeUri,
|
||||
});
|
||||
return result as bool? ?? false;
|
||||
}
|
||||
|
||||
static Future<bool> safDelete(String uri) async {
|
||||
final result = await _channel.invokeMethod('safDelete', {'uri': uri});
|
||||
return result as bool;
|
||||
|
||||
Reference in New Issue
Block a user