mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-19 22:17:29 +02:00
refactor(logging): isolate native log transport
This commit is contained in:
1 parent
a6c1409ef5
commit
08e4f1b41d
4 files changed
+97
-23
No files matched your search
@@ -0,0 +1,30 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Logging transport is independent of the bridge whose calls it records.
|
||||
class NativeLogSource {
|
||||
const NativeLogSource({
|
||||
MethodChannel channel = const MethodChannel('com.zarz.spotiflac/backend'),
|
||||
}) : _channel = channel;
|
||||
|
||||
final MethodChannel _channel;
|
||||
|
||||
Future<Map<String, dynamic>> getSince(int index) async {
|
||||
final result = await _channel.invokeMethod<Object?>('getLogsSince', {
|
||||
'index': index,
|
||||
});
|
||||
final decoded = result is String
|
||||
? (result.isEmpty ? null : jsonDecode(result))
|
||||
: result;
|
||||
if (decoded is Map) return decoded.cast<String, dynamic>();
|
||||
throw FormatException(
|
||||
'Expected map result from getLogsSince, got ${decoded.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setLoggingEnabled(bool enabled) =>
|
||||
_channel.invokeMethod<void>('setLoggingEnabled', {'enabled': enabled});
|
||||
|
||||
Future<void> clear() => _channel.invokeMethod<void>('clearLogs');
|
||||
}
|
||||
@@ -1726,17 +1726,6 @@ class PlatformBridge {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getGoLogsSince(int index) async {
|
||||
final result = await _channel.invokeMethod('getLogsSince', {
|
||||
'index': index,
|
||||
});
|
||||
return _decodeRequiredMapResult(result, 'getGoLogsSince');
|
||||
}
|
||||
|
||||
static Future<void> clearGoLogs() async {
|
||||
await _channel.invokeMethod('clearLogs');
|
||||
}
|
||||
|
||||
/// Ask the native backend to release unused memory. Best-effort:
|
||||
/// safe to call on memory pressure or when the app is backgrounded.
|
||||
static Future<void> releaseNativeMemory({bool underPressure = false}) async {
|
||||
@@ -1755,10 +1744,6 @@ class PlatformBridge {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static Future<void> setGoLoggingEnabled(bool enabled) async {
|
||||
await _channel.invokeMethod('setLoggingEnabled', {'enabled': enabled});
|
||||
}
|
||||
|
||||
static Future<void> initExtensionSystem(
|
||||
String extensionsDir,
|
||||
String dataDir, {
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:spotiflac_android/constants/app_info.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/services/native_log_source.dart';
|
||||
|
||||
const int _maxLogMessageLength = 500;
|
||||
const int _maxBufferedLogMessageLength = 4000;
|
||||
@@ -121,6 +121,7 @@ class LogBuffer extends ChangeNotifier {
|
||||
LogBuffer._internal();
|
||||
|
||||
static const int maxEntries = 500;
|
||||
static const _nativeLogs = NativeLogSource();
|
||||
static const Duration _goLogPollingInterval = Duration(milliseconds: 800);
|
||||
final Queue<LogEntry> _entries = Queue<LogEntry>();
|
||||
Timer? _goLogTimer;
|
||||
@@ -131,11 +132,7 @@ class LogBuffer extends ChangeNotifier {
|
||||
static bool get loggingEnabled => _loggingEnabled;
|
||||
static set loggingEnabled(bool value) {
|
||||
_loggingEnabled = value;
|
||||
if (value) {
|
||||
PlatformBridge.setGoLoggingEnabled(true).catchError((_) {});
|
||||
} else {
|
||||
PlatformBridge.setGoLoggingEnabled(false).catchError((_) {});
|
||||
}
|
||||
_nativeLogs.setLoggingEnabled(value).catchError((_) {});
|
||||
}
|
||||
|
||||
List<LogEntry> get entries => _entries.toList();
|
||||
@@ -209,7 +206,7 @@ class LogBuffer extends ChangeNotifier {
|
||||
|
||||
Future<void> _fetchGoLogs() async {
|
||||
try {
|
||||
final result = await PlatformBridge.getGoLogsSince(_lastGoLogIndex);
|
||||
final result = await _nativeLogs.getSince(_lastGoLogIndex);
|
||||
final logs = result['logs'] as List<dynamic>? ?? [];
|
||||
final nextIndex = result['next_index'] as int? ?? _lastGoLogIndex;
|
||||
final keepNonErrorLogs = _loggingEnabled;
|
||||
@@ -268,7 +265,7 @@ class LogBuffer extends ChangeNotifier {
|
||||
void clear() {
|
||||
_entries.clear();
|
||||
_lastGoLogIndex = 0;
|
||||
PlatformBridge.clearGoLogs().catchError((_) {});
|
||||
_nativeLogs.clear().catchError((_) {});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/services/native_log_source.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const channel = MethodChannel('com.zarz.spotiflac/backend');
|
||||
const source = NativeLogSource(channel: channel);
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
|
||||
tearDown(() => messenger.setMockMethodCallHandler(channel, null));
|
||||
|
||||
for (final encoded in [false, true]) {
|
||||
test('reads native logs and cursor (JSON=$encoded)', () async {
|
||||
final payload = {
|
||||
'logs': [
|
||||
{'level': 'ERROR', 'tag': 'Native', 'message': 'failed'},
|
||||
],
|
||||
'next_index': 8,
|
||||
};
|
||||
messenger.setMockMethodCallHandler(channel, (call) async {
|
||||
expect(call.method, 'getLogsSince');
|
||||
expect(call.arguments, {'index': 3});
|
||||
return encoded ? jsonEncode(payload) : payload;
|
||||
});
|
||||
expect(await source.getSince(3), payload);
|
||||
});
|
||||
}
|
||||
|
||||
test('keeps the native logging controls on the existing channel', () async {
|
||||
final calls = <MethodCall>[];
|
||||
messenger.setMockMethodCallHandler(channel, (call) async {
|
||||
calls.add(call);
|
||||
return null;
|
||||
});
|
||||
await source.setLoggingEnabled(true);
|
||||
await source.setLoggingEnabled(false);
|
||||
await source.clear();
|
||||
expect(calls.map((call) => call.method), [
|
||||
'setLoggingEnabled',
|
||||
'setLoggingEnabled',
|
||||
'clearLogs',
|
||||
]);
|
||||
expect(calls[0].arguments, {'enabled': true});
|
||||
expect(calls[1].arguments, {'enabled': false});
|
||||
});
|
||||
|
||||
test('reports malformed batches instead of silently losing logs', () async {
|
||||
messenger.setMockMethodCallHandler(channel, (_) async => '[]');
|
||||
await expectLater(source.getSince(0), throwsFormatException);
|
||||
});
|
||||
|
||||
test('propagates transport failures to the polling error boundary', () async {
|
||||
messenger.setMockMethodCallHandler(channel, (_) async {
|
||||
throw PlatformException(code: 'backend_unavailable');
|
||||
});
|
||||
await expectLater(source.getSince(0), throwsA(isA<PlatformException>()));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user