refactor: debug logs
This commit is contained in:
@@ -1,93 +1,105 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
|
||||
/// Handles background streaming continuation for iOS and Android
|
||||
///
|
||||
///
|
||||
/// On iOS: Uses background tasks to keep streams alive for ~30 seconds
|
||||
/// On Android: Uses foreground service notifications
|
||||
class BackgroundStreamingHandler {
|
||||
static const MethodChannel _channel = MethodChannel('conduit/background_streaming');
|
||||
|
||||
static const MethodChannel _channel = MethodChannel(
|
||||
'conduit/background_streaming',
|
||||
);
|
||||
|
||||
static BackgroundStreamingHandler? _instance;
|
||||
static BackgroundStreamingHandler get instance => _instance ??= BackgroundStreamingHandler._();
|
||||
|
||||
static BackgroundStreamingHandler get instance =>
|
||||
_instance ??= BackgroundStreamingHandler._();
|
||||
|
||||
BackgroundStreamingHandler._() {
|
||||
_setupMethodCallHandler();
|
||||
}
|
||||
|
||||
|
||||
final Set<String> _activeStreamIds = <String>{};
|
||||
final Map<String, StreamState> _streamStates = <String, StreamState>{};
|
||||
|
||||
|
||||
// Callbacks for platform-specific events
|
||||
void Function(List<String> streamIds)? onStreamsSuspending;
|
||||
void Function()? onBackgroundTaskExpiring;
|
||||
bool Function()? shouldContinueInBackground;
|
||||
|
||||
|
||||
void _setupMethodCallHandler() {
|
||||
_channel.setMethodCallHandler((call) async {
|
||||
switch (call.method) {
|
||||
case 'checkStreams':
|
||||
return _activeStreamIds.length;
|
||||
|
||||
|
||||
case 'streamsSuspending':
|
||||
final Map<String, dynamic> args = call.arguments as Map<String, dynamic>;
|
||||
final List<String> streamIds = (args['streamIds'] as List).cast<String>();
|
||||
final Map<String, dynamic> args =
|
||||
call.arguments as Map<String, dynamic>;
|
||||
final List<String> streamIds = (args['streamIds'] as List)
|
||||
.cast<String>();
|
||||
final String reason = args['reason'] as String;
|
||||
|
||||
debugPrint('Background: Streams suspending - $streamIds (reason: $reason)');
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Streams suspending - $streamIds (reason: $reason)',
|
||||
);
|
||||
onStreamsSuspending?.call(streamIds);
|
||||
|
||||
|
||||
// Save stream states for recovery
|
||||
await _saveStreamStatesForRecovery(streamIds, reason);
|
||||
break;
|
||||
|
||||
|
||||
case 'backgroundTaskExpiring':
|
||||
debugPrint('Background: Background task expiring');
|
||||
DebugLogger.stream('Background: Background task expiring');
|
||||
onBackgroundTaskExpiring?.call();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// Start background execution for given stream IDs
|
||||
Future<void> startBackgroundExecution(List<String> streamIds) async {
|
||||
if (!Platform.isIOS && !Platform.isAndroid) return;
|
||||
|
||||
|
||||
_activeStreamIds.addAll(streamIds);
|
||||
|
||||
|
||||
try {
|
||||
await _channel.invokeMethod('startBackgroundExecution', {
|
||||
'streamIds': streamIds,
|
||||
});
|
||||
|
||||
debugPrint('Background: Started background execution for ${streamIds.length} streams');
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Started background execution for ${streamIds.length} streams',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Background: Failed to start background execution: $e');
|
||||
DebugLogger.error('Background: Failed to start background execution', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Stop background execution for given stream IDs
|
||||
Future<void> stopBackgroundExecution(List<String> streamIds) async {
|
||||
if (!Platform.isIOS && !Platform.isAndroid) return;
|
||||
|
||||
|
||||
_activeStreamIds.removeAll(streamIds);
|
||||
streamIds.forEach(_streamStates.remove);
|
||||
|
||||
|
||||
try {
|
||||
await _channel.invokeMethod('stopBackgroundExecution', {
|
||||
'streamIds': streamIds,
|
||||
});
|
||||
|
||||
debugPrint('Background: Stopped background execution for ${streamIds.length} streams');
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Stopped background execution for ${streamIds.length} streams',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Background: Failed to stop background execution: $e');
|
||||
DebugLogger.error('Background: Failed to stop background execution', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Register a stream with its current state
|
||||
void registerStream(String streamId, {
|
||||
void registerStream(
|
||||
String streamId, {
|
||||
required String conversationId,
|
||||
required String messageId,
|
||||
String? sessionId,
|
||||
@@ -103,58 +115,61 @@ class BackgroundStreamingHandler {
|
||||
lastContent: lastContent ?? '',
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
|
||||
_activeStreamIds.add(streamId);
|
||||
}
|
||||
|
||||
|
||||
/// Update stream state with new chunk
|
||||
void updateStreamState(String streamId, {
|
||||
void updateStreamState(
|
||||
String streamId, {
|
||||
int? chunkSequence,
|
||||
String? content,
|
||||
String? appendedContent,
|
||||
}) {
|
||||
final state = _streamStates[streamId];
|
||||
if (state == null) return;
|
||||
|
||||
|
||||
_streamStates[streamId] = state.copyWith(
|
||||
lastChunkSequence: chunkSequence ?? state.lastChunkSequence,
|
||||
lastContent: appendedContent != null
|
||||
lastContent: appendedContent != null
|
||||
? (state.lastContent + appendedContent)
|
||||
: (content ?? state.lastContent),
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Unregister a stream when it completes
|
||||
void unregisterStream(String streamId) {
|
||||
_activeStreamIds.remove(streamId);
|
||||
_streamStates.remove(streamId);
|
||||
}
|
||||
|
||||
|
||||
/// Get current stream state for recovery
|
||||
StreamState? getStreamState(String streamId) {
|
||||
return _streamStates[streamId];
|
||||
}
|
||||
|
||||
|
||||
/// Keep alive the background task (iOS only)
|
||||
Future<void> keepAlive() async {
|
||||
if (!Platform.isIOS) return;
|
||||
|
||||
|
||||
try {
|
||||
await _channel.invokeMethod('keepAlive');
|
||||
} catch (e) {
|
||||
debugPrint('Background: Failed to keep alive: $e');
|
||||
DebugLogger.error('Background: Failed to keep alive', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Recover stream states from previous app session
|
||||
Future<List<StreamState>> recoverStreamStates() async {
|
||||
if (!Platform.isIOS && !Platform.isAndroid) return [];
|
||||
|
||||
|
||||
try {
|
||||
final List<dynamic>? states = await _channel.invokeMethod('recoverStreamStates');
|
||||
final List<dynamic>? states = await _channel.invokeMethod(
|
||||
'recoverStreamStates',
|
||||
);
|
||||
if (states == null) return [];
|
||||
|
||||
|
||||
final recovered = <StreamState>[];
|
||||
for (final stateData in states) {
|
||||
final map = stateData as Map<String, dynamic>;
|
||||
@@ -164,39 +179,44 @@ class BackgroundStreamingHandler {
|
||||
_streamStates[state.streamId] = state;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('Background: Recovered ${recovered.length} stream states');
|
||||
|
||||
DebugLogger.stream(
|
||||
'Background: Recovered ${recovered.length} stream states',
|
||||
);
|
||||
return recovered;
|
||||
} catch (e) {
|
||||
debugPrint('Background: Failed to recover stream states: $e');
|
||||
DebugLogger.error('Background: Failed to recover stream states', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Save stream states for recovery after app restart
|
||||
Future<void> _saveStreamStatesForRecovery(List<String> streamIds, String reason) async {
|
||||
Future<void> _saveStreamStatesForRecovery(
|
||||
List<String> streamIds,
|
||||
String reason,
|
||||
) async {
|
||||
final statesToSave = streamIds
|
||||
.map((id) => _streamStates[id])
|
||||
.where((state) => state != null)
|
||||
.map((state) => state!.toMap())
|
||||
.toList();
|
||||
|
||||
|
||||
try {
|
||||
await _channel.invokeMethod('saveStreamStates', {
|
||||
'states': statesToSave,
|
||||
'reason': reason,
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('Background: Failed to save stream states: $e');
|
||||
DebugLogger.error('Background: Failed to save stream states', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Check if any streams are currently active
|
||||
bool get hasActiveStreams => _activeStreamIds.isNotEmpty;
|
||||
|
||||
|
||||
/// Get list of active stream IDs
|
||||
List<String> get activeStreamIds => _activeStreamIds.toList();
|
||||
|
||||
|
||||
/// Clear all stream data (usually on app termination)
|
||||
void clearAll() {
|
||||
_activeStreamIds.clear();
|
||||
@@ -213,7 +233,7 @@ class StreamState {
|
||||
final int lastChunkSequence;
|
||||
final String lastContent;
|
||||
final DateTime timestamp;
|
||||
|
||||
|
||||
const StreamState({
|
||||
required this.streamId,
|
||||
required this.conversationId,
|
||||
@@ -223,7 +243,7 @@ class StreamState {
|
||||
required this.lastContent,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
|
||||
StreamState copyWith({
|
||||
String? streamId,
|
||||
String? conversationId,
|
||||
@@ -243,7 +263,7 @@ class StreamState {
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'streamId': streamId,
|
||||
@@ -255,7 +275,7 @@ class StreamState {
|
||||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static StreamState? fromMap(Map<String, dynamic> map) {
|
||||
try {
|
||||
return StreamState(
|
||||
@@ -270,20 +290,20 @@ class StreamState {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to parse StreamState from map: $e');
|
||||
DebugLogger.error('Failed to parse StreamState from map', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Check if this state is stale (older than threshold)
|
||||
bool isStale({Duration threshold = const Duration(minutes: 5)}) {
|
||||
return DateTime.now().difference(timestamp) > threshold;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StreamState(streamId: $streamId, conversationId: $conversationId, '
|
||||
'messageId: $messageId, sequence: $lastChunkSequence, '
|
||||
'contentLength: ${lastContent.length}, timestamp: $timestamp)';
|
||||
'messageId: $messageId, sequence: $lastChunkSequence, '
|
||||
'contentLength: ${lastContent.length}, timestamp: $timestamp)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user