fix: handle background isolate shutdown on iOS
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import 'dart:isolate';
|
||||
import '../utils/isolate_helper.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'plex_client.dart';
|
||||
@@ -76,7 +76,7 @@ class BifThumbnailService {
|
||||
appLogger.w('BIF file too large (${bytes.length} bytes), skipping');
|
||||
return;
|
||||
}
|
||||
_entries = await Isolate.run(() => _parseBifBytes(bytes));
|
||||
_entries = await tryIsolateRun(() => _parseBifBytes(bytes));
|
||||
} catch (e) {
|
||||
appLogger.w('BIF download/parse failed', error: e);
|
||||
}
|
||||
|
||||
@@ -1085,6 +1085,7 @@ class DownloadManagerService {
|
||||
String? errorMessage,
|
||||
String? currentFile,
|
||||
}) {
|
||||
if (_disposed) return;
|
||||
_progressController.add(
|
||||
DownloadProgress(
|
||||
globalKey: globalKey,
|
||||
@@ -1118,6 +1119,7 @@ class DownloadManagerService {
|
||||
|
||||
/// Emit progress update with artwork paths so DownloadProvider can sync
|
||||
void _emitProgressWithArtwork(String globalKey, {String? thumbPath}) {
|
||||
if (_disposed) return;
|
||||
// Emit a progress update containing artwork path
|
||||
// The status is preserved as downloading since artwork is just one step
|
||||
_progressController.add(
|
||||
@@ -1263,6 +1265,7 @@ class DownloadManagerService {
|
||||
|
||||
/// Emit deletion progress update
|
||||
void _emitDeletionProgress(DeletionProgress progress) {
|
||||
if (_disposed) return;
|
||||
_deletionProgressController.add(progress);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
import '../utils/isolate_helper.dart';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
@@ -42,7 +42,7 @@ class PlexApiCache {
|
||||
final result = await (_db.select(_db.apiCache)..where((t) => t.cacheKey.equals(key))).getSingleOrNull();
|
||||
|
||||
if (result != null) {
|
||||
return await Isolate.run(() => jsonDecode(result.data) as Map<String, dynamic>);
|
||||
return await tryIsolateRun(() => jsonDecode(result.data) as Map<String, dynamic>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class PlexApiCache {
|
||||
/// Cache a response for an endpoint
|
||||
Future<void> put(String serverId, String endpoint, Map<String, dynamic> data) async {
|
||||
final key = _buildKey(serverId, endpoint);
|
||||
final encoded = await Isolate.run(() => jsonEncode(data));
|
||||
final encoded = await tryIsolateRun(() => jsonEncode(data));
|
||||
await _db
|
||||
.into(_db.apiCache)
|
||||
.insertOnConflictUpdate(ApiCacheCompanion(cacheKey: Value(key), data: Value(encoded), cachedAt: Value(DateTime.now())));
|
||||
@@ -145,7 +145,7 @@ class PlexApiCache {
|
||||
|
||||
if (entries.isEmpty) return {};
|
||||
|
||||
return await Isolate.run(() {
|
||||
return await tryIsolateRun(() {
|
||||
final result = <String, PlexMetadata>{};
|
||||
for (final (serverId, ratingKey, rawData) in entries) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
import '../utils/isolate_helper.dart';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@@ -112,7 +112,7 @@ class ConnectionTestResult {
|
||||
ConnectionTestResult({required this.success, required this.latencyMs, this.error});
|
||||
}
|
||||
|
||||
// Top-level function required by compute()
|
||||
// Top-level function required by tryIsolateRun()
|
||||
String _decodeUtf8(List<int> bytes) {
|
||||
return utf8.decode(bytes, allowMalformed: true);
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class PlexClient {
|
||||
ResponseBody responseBody,
|
||||
) {
|
||||
if (responseBytes.length > 50 * 1024) {
|
||||
return compute(_decodeUtf8, responseBytes);
|
||||
return tryIsolateRun(() => _decodeUtf8(responseBytes));
|
||||
}
|
||||
return utf8.decode(responseBytes, allowMalformed: true);
|
||||
}
|
||||
@@ -993,7 +993,7 @@ class PlexClient {
|
||||
final response = await _dio.get('/library/onDeck');
|
||||
final sid = serverId;
|
||||
final sname = serverName;
|
||||
return await Isolate.run(() => _processOnDeckResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
return await tryIsolateRun(() => _processOnDeckResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
}
|
||||
|
||||
/// Get children of a metadata item (e.g., seasons for a show, episodes for a season)
|
||||
@@ -1481,7 +1481,7 @@ class PlexClient {
|
||||
);
|
||||
final sid = serverId;
|
||||
final sname = serverName;
|
||||
return await Isolate.run(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
return await tryIsolateRun(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to get library hubs: $e');
|
||||
}
|
||||
@@ -1496,7 +1496,7 @@ class PlexClient {
|
||||
final response = await _dio.get('/hubs', queryParameters: {'count': limit, 'includeGuids': 1});
|
||||
final sid = serverId;
|
||||
final sname = serverName;
|
||||
return await Isolate.run(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
return await tryIsolateRun(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to get global hubs: $e');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'dart:isolate';
|
||||
|
||||
/// Runs [computation] in a background isolate via [Isolate.run].
|
||||
///
|
||||
/// Falls back to synchronous execution when the isolate infrastructure is
|
||||
/// unavailable (e.g. iOS killed background isolates while the app was
|
||||
/// suspended).
|
||||
Future<R> tryIsolateRun<R>(R Function() computation) async {
|
||||
try {
|
||||
return await Isolate.run(computation);
|
||||
} on StateError {
|
||||
return computation();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user