Basic framework
This commit is contained in:
27
finlog_app/fluttery/lib/environment.dart
Normal file
27
finlog_app/fluttery/lib/environment.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
|
||||
/// Abstract Environment contract
|
||||
abstract class Environment extends Service {
|
||||
/// Platform checks
|
||||
bool get isAndroid;
|
||||
|
||||
bool get isIOS;
|
||||
|
||||
/// Build mode
|
||||
bool get isDebug;
|
||||
|
||||
bool get isRelease;
|
||||
|
||||
bool get isProfile;
|
||||
|
||||
/// App info
|
||||
Future<void> loadPackageInfo();
|
||||
|
||||
String get appName;
|
||||
|
||||
String get packageName;
|
||||
|
||||
String get version;
|
||||
|
||||
String get buildNumber;
|
||||
}
|
||||
@@ -1,5 +1,73 @@
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
library;
|
||||
|
||||
import 'package:fluttery/environment.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
import 'package:fluttery/preferences.dart';
|
||||
import 'package:fluttery/secure_storage.dart';
|
||||
import 'package:fluttery/src/logger/logger_impl.dart';
|
||||
import 'package:fluttery/src/preferences/preferences_impl.dart';
|
||||
import 'package:fluttery/src/storage/secure/secure_storage_impl.dart';
|
||||
import 'package:fluttery/src/system/environment/environment_impl.dart';
|
||||
import 'package:fluttery/src/system/worker/worker_impl.dart';
|
||||
import 'package:fluttery/worker.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
|
||||
/// A class to manage services.
|
||||
class App {
|
||||
static final _AppService _appService = _AppService();
|
||||
|
||||
/// Registers a service with a factory function to instantiate the implementation.
|
||||
///
|
||||
/// This ensures that the implementation is created when the service is requested.
|
||||
///
|
||||
/// `implFactory` - A factory method to create the service implementation.
|
||||
static void registerService<T extends Service>(T Function() implFactory) {
|
||||
_appService.registerSingleton<T>(implFactory);
|
||||
}
|
||||
|
||||
/// Retrieves the registered service.
|
||||
///
|
||||
/// Returns an instance of the registered service.
|
||||
static T service<T extends Service>() {
|
||||
return _appService.resolve<T>();
|
||||
}
|
||||
|
||||
/// Registers the default services required by the application.
|
||||
static void registerDefaultServices() {
|
||||
registerService<Logger>(() => LoggerImpl());
|
||||
registerService<Preferences>(() => PreferencesImpl());
|
||||
registerService<Environment>(() => EnvironmentImpl());
|
||||
registerService<SecureStorage>(() => SecureStorageImpl());
|
||||
registerService<Worker>(() => WorkerImpl());
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract class to represent a service.
|
||||
abstract class Service {}
|
||||
|
||||
/// Internal class to manage the registration and resolution of services.
|
||||
class _AppService {
|
||||
static _AppService? _singleton;
|
||||
|
||||
static final KiwiContainer _kiwi = KiwiContainer();
|
||||
|
||||
/// Factory constructor to ensure singleton instance of _AppService.
|
||||
factory _AppService() => _singleton ??= _AppService._();
|
||||
|
||||
/// Private constructor.
|
||||
_AppService._();
|
||||
|
||||
/// Registers a singleton service with a factory function to create the instance.
|
||||
///
|
||||
/// `serviceFactory` - A factory method to create the service implementation.
|
||||
void registerSingleton<T extends Service>(T Function() serviceFactory) {
|
||||
_kiwi.registerSingleton<T>((c) => serviceFactory());
|
||||
}
|
||||
|
||||
/// Resolves and retrieves the registered service.
|
||||
///
|
||||
/// Returns an instance of the registered service.
|
||||
T resolve<T extends Service>() {
|
||||
return _kiwi.resolve<T>();
|
||||
}
|
||||
}
|
||||
|
||||
33
finlog_app/fluttery/lib/logger.dart
Normal file
33
finlog_app/fluttery/lib/logger.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:logging/logging.dart' as lib;
|
||||
|
||||
/// Abstract class for logging service.
|
||||
/// Provides methods for different log levels and configuration.
|
||||
abstract class Logger extends Service {
|
||||
/// Logs an informational message.
|
||||
///
|
||||
/// [message] is the information to log.
|
||||
void info(String message);
|
||||
|
||||
/// Logs a warning message.
|
||||
///
|
||||
/// [message] is the warning to log.
|
||||
void warning(String message);
|
||||
|
||||
/// Logs an error message with optional error and stack trace.
|
||||
///
|
||||
/// [message] is the error message to log.
|
||||
/// [error] is the optional error object associated with this log entry.
|
||||
/// [stackTrace] is the optional stack trace associated with this log entry.
|
||||
void error(String message, [Object? error, StackTrace? stackTrace]);
|
||||
|
||||
/// Logs a debug message.
|
||||
///
|
||||
/// [message] is the debug message to log.
|
||||
void debug(String message);
|
||||
|
||||
/// Sets the log level for the logger.
|
||||
///
|
||||
/// [level] is the new log level to set.
|
||||
void setLogLevel(lib.Level level);
|
||||
}
|
||||
40
finlog_app/fluttery/lib/preferences.dart
Normal file
40
finlog_app/fluttery/lib/preferences.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
|
||||
/// providing methods for managing persistent storage of key-value pairs.
|
||||
abstract class Preferences implements Service {
|
||||
/// Stores a string value with the given [key].
|
||||
Future<void> setString(String key, String value);
|
||||
|
||||
/// Retrieves the string value associated with the given [key].
|
||||
Future<String?> getString(String key);
|
||||
|
||||
/// Stores an integer value with the given [key].
|
||||
Future<void> setInt(String key, int value);
|
||||
|
||||
/// Retrieves the integer value associated with the given [key].
|
||||
Future<int?> getInt(String key);
|
||||
|
||||
/// Stores a boolean value with the given [key].
|
||||
Future<void> setBool(String key, bool value);
|
||||
|
||||
/// Retrieves the boolean value associated with the given [key].
|
||||
Future<bool?> getBool(String key);
|
||||
|
||||
/// Stores a double value with the given [key].
|
||||
Future<void> setDouble(String key, double value);
|
||||
|
||||
/// Retrieves the double value associated with the given [key].
|
||||
Future<double?> getDouble(String key);
|
||||
|
||||
/// Stores a list of strings with the given [key].
|
||||
Future<void> setStringList(String key, List<String> value);
|
||||
|
||||
/// Retrieves the list of strings associated with the given [key].
|
||||
Future<List<String>?> getStringList(String key);
|
||||
|
||||
/// Removes the key-value pair associated with the given [key].
|
||||
Future<void> remove(String key);
|
||||
|
||||
/// Clears all key-value pairs in the preferences.
|
||||
Future<void> clear();
|
||||
}
|
||||
75
finlog_app/fluttery/lib/secure_storage.dart
Normal file
75
finlog_app/fluttery/lib/secure_storage.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
|
||||
/// Interface for secure storage operations.
|
||||
///
|
||||
/// Provides methods for securely storing and retrieving sensitive data
|
||||
/// like passwords, tokens, API keys, etc. Data stored through this interface
|
||||
/// is encrypted and stored in the device's secure storage (Keychain on iOS,
|
||||
/// Keystore on Android).
|
||||
abstract class SecureStorage implements Service {
|
||||
/// Stores a string value securely with the given [key].
|
||||
///
|
||||
/// Returns a Future that completes when the value is successfully stored.
|
||||
/// Throws an exception if the storage operation fails.
|
||||
Future<void> write(String key, String value);
|
||||
|
||||
/// Retrieves the securely stored string value for the given [key].
|
||||
///
|
||||
/// Returns the stored value if found, null otherwise.
|
||||
/// Throws an exception if the retrieval operation fails.
|
||||
Future<String?> read(String key);
|
||||
|
||||
/// Stores an integer value securely with the given [key].
|
||||
///
|
||||
/// The integer is converted to a string for storage.
|
||||
Future<void> writeInt(String key, int value);
|
||||
|
||||
/// Retrieves the securely stored integer value for the given [key].
|
||||
///
|
||||
/// Returns the stored integer if found and valid, null otherwise.
|
||||
Future<int?> readInt(String key);
|
||||
|
||||
/// Stores a boolean value securely with the given [key].
|
||||
///
|
||||
/// The boolean is converted to a string for storage.
|
||||
Future<void> writeBool(String key, bool value);
|
||||
|
||||
/// Retrieves the securely stored boolean value for the given [key].
|
||||
///
|
||||
/// Returns the stored boolean if found and valid, null otherwise.
|
||||
Future<bool?> readBool(String key);
|
||||
|
||||
/// Stores a double value securely with the given [key].
|
||||
///
|
||||
/// The double is converted to a string for storage.
|
||||
Future<void> writeDouble(String key, double value);
|
||||
|
||||
/// Retrieves the securely stored double value for the given [key].
|
||||
///
|
||||
/// Returns the stored double if found and valid, null otherwise.
|
||||
Future<double?> readDouble(String key);
|
||||
|
||||
/// Removes the securely stored value for the given [key].
|
||||
///
|
||||
/// Returns a Future that completes when the value is successfully removed.
|
||||
Future<void> delete(String key);
|
||||
|
||||
/// Removes all securely stored values.
|
||||
///
|
||||
/// Returns a Future that completes when all values are successfully removed.
|
||||
/// Use with caution as this operation cannot be undone.
|
||||
Future<void> deleteAll();
|
||||
|
||||
/// Returns all keys incl. values in secure storage
|
||||
Future<Set<String>> readAll();
|
||||
|
||||
/// Returns all keys currently stored in secure storage.
|
||||
///
|
||||
/// Useful for debugging or migration purposes.
|
||||
Future<Set<String>> readAllKeys();
|
||||
|
||||
/// Checks if a value exists for the given [key].
|
||||
///
|
||||
/// Returns true if a value exists, false otherwise.
|
||||
Future<bool> containsKey(String key);
|
||||
}
|
||||
57
finlog_app/fluttery/lib/src/logger/logger_impl.dart
Normal file
57
finlog_app/fluttery/lib/src/logger/logger_impl.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
import 'package:logging/logging.dart' as lib;
|
||||
|
||||
// ignore_for_file: avoid_print
|
||||
class LoggerImpl implements Logger {
|
||||
final lib.Logger _logger;
|
||||
|
||||
// coverage:ignore-start
|
||||
/// Constructor
|
||||
LoggerImpl() : _logger = lib.Logger("Logger") {
|
||||
_logger.onRecord.listen((lib.LogRecord record) {
|
||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||
if (record.error != null) {
|
||||
print('Error: ${record.error}');
|
||||
}
|
||||
if (record.stackTrace != null) {
|
||||
print('Stack Trace: ${record.stackTrace}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// coverage:ignore-end
|
||||
@visibleForTesting
|
||||
factory LoggerImpl.forTest(lib.Logger logger) {
|
||||
final instance = LoggerImpl._internal(logger);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Private internal constructor
|
||||
LoggerImpl._internal(this._logger);
|
||||
|
||||
@override
|
||||
void info(String message) {
|
||||
_logger.info(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void warning(String message) {
|
||||
_logger.warning(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void error(String message, [Object? error, StackTrace? stackTrace]) {
|
||||
_logger.severe(message, error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void debug(String message) {
|
||||
_logger.fine(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void setLogLevel(lib.Level level) {
|
||||
_logger.level = level;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:fluttery/preferences.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class PreferencesImpl implements Preferences {
|
||||
late final SharedPreferences _prefs;
|
||||
bool _initialized = false;
|
||||
|
||||
Future<void> _ensureInitialized() async {
|
||||
if (!_initialized) {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setString(String key, String value) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.setString(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getString(String key) async {
|
||||
await _ensureInitialized();
|
||||
return _prefs.getString(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setInt(String key, int value) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.setInt(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int?> getInt(String key) async {
|
||||
await _ensureInitialized();
|
||||
return _prefs.getInt(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setBool(String key, bool value) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.setBool(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> getBool(String key) async {
|
||||
await _ensureInitialized();
|
||||
return _prefs.getBool(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setDouble(String key, double value) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.setDouble(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<double?> getDouble(String key) async {
|
||||
await _ensureInitialized();
|
||||
return _prefs.getDouble(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setStringList(String key, List<String> value) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.setStringList(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>?> getStringList(String key) async {
|
||||
await _ensureInitialized();
|
||||
return _prefs.getStringList(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await _ensureInitialized();
|
||||
await _prefs.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:fluttery/secure_storage.dart';
|
||||
|
||||
class SecureStorageImpl implements SecureStorage {
|
||||
final FlutterSecureStorage _secureStorage;
|
||||
|
||||
/// Constructor - creates a single instance with default FlutterSecureStorage
|
||||
SecureStorageImpl() : _secureStorage = const FlutterSecureStorage();
|
||||
|
||||
/// Testing constructor
|
||||
@visibleForTesting
|
||||
SecureStorageImpl.forTesting({required FlutterSecureStorage instance})
|
||||
: _secureStorage = instance;
|
||||
|
||||
@override
|
||||
Future<void> write(String key, String value) async {
|
||||
await _secureStorage.write(key: key, value: value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> read(String key) async {
|
||||
return await _secureStorage.read(key: key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> containsKey(String key) async {
|
||||
return await _secureStorage.containsKey(key: key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String key) async {
|
||||
await _secureStorage.delete(key: key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAll() async {
|
||||
await _secureStorage.deleteAll();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<String>> readAll() async {
|
||||
final allData = await _secureStorage.readAll();
|
||||
return allData.keys.toSet();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<String>> readAllKeys() async {
|
||||
final allData = await _secureStorage.readAll();
|
||||
return allData.keys.toSet();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeInt(String key, int value) async {
|
||||
await _secureStorage.write(key: key, value: value.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int?> readInt(String key) async {
|
||||
final value = await _secureStorage.read(key: key);
|
||||
if (value == null) return null;
|
||||
return int.tryParse(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeBool(String key, bool value) async {
|
||||
await _secureStorage.write(key: key, value: value.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> readBool(String key) async {
|
||||
final value = await _secureStorage.read(key: key);
|
||||
if (value == null) return null;
|
||||
if (value.toLowerCase() == 'true') return true;
|
||||
if (value.toLowerCase() == 'false') return false;
|
||||
return null; // Invalid boolean value
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeDouble(String key, double value) async {
|
||||
await _secureStorage.write(key: key, value: value.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<double?> readDouble(String key) async {
|
||||
final value = await _secureStorage.read(key: key);
|
||||
if (value == null) return null;
|
||||
return double.tryParse(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show kDebugMode, kReleaseMode, kProfileMode;
|
||||
|
||||
import 'package:fluttery/environment.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class EnvironmentImpl implements Environment {
|
||||
PackageInfo? _packageInfo;
|
||||
|
||||
@override
|
||||
bool get isAndroid => Platform.isAndroid;
|
||||
|
||||
@override
|
||||
bool get isIOS => Platform.isIOS;
|
||||
|
||||
@override
|
||||
Future<void> loadPackageInfo() async {
|
||||
_packageInfo = await PackageInfo.fromPlatform();
|
||||
}
|
||||
|
||||
@override
|
||||
String get appName => _packageInfo?.appName ?? 'Unknown';
|
||||
|
||||
@override
|
||||
String get packageName => _packageInfo?.packageName ?? 'Unknown';
|
||||
|
||||
@override
|
||||
String get version => _packageInfo?.version ?? '0.0.0';
|
||||
|
||||
@override
|
||||
String get buildNumber => _packageInfo?.buildNumber ?? '0';
|
||||
|
||||
@override
|
||||
bool get isDebug => kDebugMode;
|
||||
|
||||
@override
|
||||
bool get isRelease => kReleaseMode;
|
||||
|
||||
@override
|
||||
bool get isProfile => kProfileMode;
|
||||
}
|
||||
189
finlog_app/fluttery/lib/src/system/worker/worker_impl.dart
Normal file
189
finlog_app/fluttery/lib/src/system/worker/worker_impl.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/services.dart'
|
||||
show ServicesBinding, RootIsolateToken, BackgroundIsolateBinaryMessenger;
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
import 'package:fluttery/worker.dart';
|
||||
|
||||
class WorkerImpl implements Worker {
|
||||
final Logger _logger;
|
||||
|
||||
WorkerImpl({
|
||||
this.defaultTimeout,
|
||||
this.maxHistory = 100,
|
||||
RootIsolateToken? rootToken,
|
||||
}) : _rootToken = rootToken ?? ServicesBinding.rootIsolateToken,
|
||||
_logger = App.service<Logger>();
|
||||
|
||||
final Duration? defaultTimeout;
|
||||
final int maxHistory;
|
||||
|
||||
// Captured from the root isolate (may be null in some test envs)
|
||||
final RootIsolateToken? _rootToken;
|
||||
|
||||
final Map<String, WorkerInfo> _active = {};
|
||||
final List<WorkerInfo> _history = [];
|
||||
|
||||
@override
|
||||
Future<T> spawn<T>(
|
||||
String debugName,
|
||||
FutureOr<T> Function() task, {
|
||||
void Function()? preTask,
|
||||
Duration? timeout,
|
||||
}) {
|
||||
final id = _generateWorkerId();
|
||||
final started = DateTime.now();
|
||||
|
||||
_logger.debug('Spawning worker "$debugName" ($id)');
|
||||
_registerActiveWorker(id, debugName, started);
|
||||
|
||||
final future = _executeWithTimeout(
|
||||
id,
|
||||
debugName,
|
||||
task,
|
||||
preTask,
|
||||
timeout ?? defaultTimeout,
|
||||
);
|
||||
|
||||
_attachCompletionHandlers(id, debugName, future);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
String _generateWorkerId() {
|
||||
return 'iso-${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
|
||||
void _registerActiveWorker(String id, String debugName, DateTime started) {
|
||||
_active[id] = WorkerInfo(
|
||||
id: id,
|
||||
name: debugName,
|
||||
startedAt: started,
|
||||
status: WorkerStatus.running,
|
||||
);
|
||||
_logger.debug('Registered worker "$debugName" ($id)');
|
||||
}
|
||||
|
||||
Future<T> _executeWithTimeout<T>(
|
||||
String id,
|
||||
String debugName,
|
||||
FutureOr<T> Function() task,
|
||||
void Function()? preTask,
|
||||
Duration? timeout,
|
||||
) {
|
||||
_logger.debug(
|
||||
'Executing worker "$debugName" ($id) with timeout: ${timeout?.inSeconds ?? "none"} seconds',
|
||||
);
|
||||
final future = _executeInIsolate(debugName, task, preTask);
|
||||
|
||||
return timeout == null ? future : future.timeout(timeout);
|
||||
}
|
||||
|
||||
Future<T> _executeInIsolate<T>(
|
||||
String debugName,
|
||||
FutureOr<T> Function() task,
|
||||
void Function()? preTask,
|
||||
) {
|
||||
final token = _rootToken; // captured into closure
|
||||
_logger.debug('Starting isolate for worker "$debugName"');
|
||||
|
||||
return Isolate.run<T>(() async {
|
||||
// Initialize platform channels for this background isolate.
|
||||
if (token != null) {
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
||||
}
|
||||
// Now it's safe to touch plugins (e.g., SharedPreferences).
|
||||
App.registerDefaultServices();
|
||||
if (preTask != null) {
|
||||
_logger.debug('Executing pre-task for worker "$debugName"');
|
||||
preTask();
|
||||
}
|
||||
return await Future.sync(task);
|
||||
}, debugName: debugName);
|
||||
}
|
||||
|
||||
void _attachCompletionHandlers<T>(
|
||||
String id,
|
||||
String debugName,
|
||||
Future<T> future,
|
||||
) {
|
||||
future
|
||||
.then((_) {
|
||||
_logger.debug('Worker "$debugName" ($id) completed successfully');
|
||||
_finish(id, status: WorkerStatus.completed);
|
||||
})
|
||||
.catchError((e, st) {
|
||||
final status = e is TimeoutException
|
||||
? WorkerStatus.timedOut
|
||||
: WorkerStatus.failed;
|
||||
|
||||
_finish(id, status: status, error: e, stack: st);
|
||||
_logWorkerError(debugName, id, e, st);
|
||||
});
|
||||
}
|
||||
|
||||
void _logWorkerError(
|
||||
String debugName,
|
||||
String id,
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
// Best-effort logging
|
||||
try {
|
||||
App.service<Logger>().error(
|
||||
'Worker job "$debugName" ($id) failed: $error',
|
||||
stackTrace,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _finish(
|
||||
String id, {
|
||||
required WorkerStatus status,
|
||||
Object? error,
|
||||
StackTrace? stack,
|
||||
}) {
|
||||
final prev = _active.remove(id);
|
||||
final endedAt = DateTime.now();
|
||||
final info = WorkerInfo(
|
||||
id: prev?.id ?? id,
|
||||
name: prev?.name ?? 'unknown',
|
||||
startedAt: prev?.startedAt ?? endedAt,
|
||||
status: status,
|
||||
endedAt: endedAt,
|
||||
error: error,
|
||||
stackTrace: stack,
|
||||
);
|
||||
_history.insert(0, info);
|
||||
if (_history.length > maxHistory) {
|
||||
_history.removeRange(maxHistory, _history.length);
|
||||
}
|
||||
_logger.debug('Worker "${prev?.name}" ($id) finished with status: $status');
|
||||
}
|
||||
|
||||
@override
|
||||
List<WorkerInfo> getActiveWorkers() =>
|
||||
_active.values.toList()
|
||||
..sort((a, b) => a.startedAt.compareTo(b.startedAt));
|
||||
|
||||
@override
|
||||
List<WorkerInfo> getAllWorkers() => [...getActiveWorkers(), ..._history];
|
||||
|
||||
@override
|
||||
WorkerInfo? getWorker(String id) {
|
||||
final active = _active[id];
|
||||
if (active != null) return active;
|
||||
for (final w in _history) {
|
||||
if (w.id == id) return w;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void purge({Duration maxAge = const Duration(minutes: 30)}) {
|
||||
final cutoff = DateTime.now().subtract(maxAge);
|
||||
_logger.debug('Purging workers older than $maxAge');
|
||||
_history.removeWhere((w) => (w.endedAt ?? w.startedAt).isBefore(cutoff));
|
||||
}
|
||||
}
|
||||
47
finlog_app/fluttery/lib/worker.dart
Normal file
47
finlog_app/fluttery/lib/worker.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'dart:async';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
|
||||
abstract class Worker extends Service {
|
||||
Future<T> spawn<T>(
|
||||
String debugName,
|
||||
FutureOr<T> Function() task, {
|
||||
void Function()? preTask,
|
||||
Duration? timeout, // per-job override
|
||||
});
|
||||
|
||||
/// Currently running jobs.
|
||||
List<WorkerInfo> getActiveWorkers();
|
||||
|
||||
/// All known jobs (active + completed + failed), up to a capped history.
|
||||
List<WorkerInfo> getAllWorkers();
|
||||
|
||||
/// Optional: get a single worker by id.
|
||||
WorkerInfo? getWorker(String id);
|
||||
|
||||
/// Remove completed/failed jobs older than [maxAge] from history.
|
||||
void purge({Duration maxAge = const Duration(minutes: 30)});
|
||||
}
|
||||
|
||||
enum WorkerStatus { running, completed, failed, timedOut }
|
||||
|
||||
class WorkerInfo {
|
||||
WorkerInfo({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.startedAt,
|
||||
required this.status,
|
||||
this.endedAt,
|
||||
this.error,
|
||||
this.stackTrace,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final DateTime startedAt;
|
||||
final WorkerStatus status;
|
||||
final DateTime? endedAt;
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
Duration get duration => ((endedAt ?? DateTime.now()).difference(startedAt));
|
||||
}
|
||||
Reference in New Issue
Block a user