diff --git a/lib/services/bundled_mining_service.dart b/lib/services/bundled_mining_service.dart index be30c08..b243d09 100644 --- a/lib/services/bundled_mining_service.dart +++ b/lib/services/bundled_mining_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math' as math; import 'package:logger/logger.dart'; import 'package:path/path.dart' as p; @@ -142,6 +143,7 @@ class BundledMiningService implements MiningService { final logFile = await AppPaths.minerLogFile(); final launcherLogFile = await AppPaths.minerLauncherLogFile(); await launcherLogFile.parent.create(recursive: true); + await _prepareBinary(binary); final args = [ '--coin', @@ -180,6 +182,10 @@ class BundledMiningService implements MiningService { } try { + await _appendLauncherLog( + launcherLogFile, + 'launch request: binary="$binary" cwd="${p.dirname(binary)}" args=${args.join(' ')}', + ); final process = await Process.start( binary, args, @@ -191,11 +197,26 @@ class BundledMiningService implements MiningService { final earlyExit = await _waitForEarlyExit(process); if (earlyExit != null) { _logger.w('xmrig exited immediately with code $earlyExit'); + await _appendLauncherLog( + launcherLogFile, + 'xmrig exited immediately with code $earlyExit', + ); return false; } - return _waitForApi(config); + final ready = await _waitForApi(config); + if (!ready) { + await _appendLauncherLog( + launcherLogFile, + 'xmrig process did not expose HTTP API on 127.0.0.1:${config.apiPort} within timeout', + ); + } + return ready; } catch (error) { _logger.w('Failed to start xmrig: $error'); + await _appendLauncherLog( + launcherLogFile, + 'Failed to start xmrig: $error', + ); return false; } } @@ -224,6 +245,15 @@ class BundledMiningService implements MiningService { ); } + Future _appendLauncherLog(File file, String line) async { + try { + await file.writeAsString( + '[${DateTime.now().toIso8601String()}] $line\n', + mode: FileMode.writeOnlyAppend, + ); + } catch (_) {} + } + Future _waitForEarlyExit(Process process) async { const grace = Duration(seconds: 2); final result = await Future.any([ @@ -243,6 +273,28 @@ class BundledMiningService implements MiningService { return false; } + Future _prepareBinary(String binary) async { + if (!Platform.isLinux) { + return; + } + try { + final file = File(binary); + final stat = await file.stat(); + final mode = stat.mode; + final execBits = 0x49; // user/group/other execute + if ((mode & execBits) == execBits) { + return; + } + await Process.run( + 'chmod', + ['755', binary], + runInShell: true, + ); + } catch (error) { + _logger.w('Failed to ensure xmrig executable bit: $error'); + } + } + Future _waitForExit(int pid, MinerLaunchConfig? config) async { for (var i = 0; i < 20; i++) { final alive = _isProcessAlive(pid); @@ -319,6 +371,10 @@ class BundledMiningService implements MiningService { if (index < 0 || index >= values.length) { return null; } - return (values[index] as num?)?.toDouble(); + final value = (values[index] as num?)?.toDouble(); + if (value == null) { + return null; + } + return math.max(0, value); } } diff --git a/lib/ui/screens/mining_screen.dart b/lib/ui/screens/mining_screen.dart index 3a50d04..4f3d4af 100644 --- a/lib/ui/screens/mining_screen.dart +++ b/lib/ui/screens/mining_screen.dart @@ -419,13 +419,18 @@ class _MiningScreenState extends ConsumerState { Future _startMining() async { final l10n = context.l10n; final started = await ref.read(miningControllerProvider.notifier).start(); + final miningState = ref.read(miningControllerProvider); if (!mounted) { return; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - started ? l10n.miningMinerStartSuccess : l10n.miningMinerStartFailure, + started + ? l10n.miningMinerStartSuccess + : (miningState.error?.isNotEmpty == true + ? '${l10n.miningMinerStartFailure}: ${miningState.error}' + : l10n.miningMinerStartFailure), ), ), ); @@ -434,13 +439,18 @@ class _MiningScreenState extends ConsumerState { Future _stopMining() async { final l10n = context.l10n; final stopped = await ref.read(miningControllerProvider.notifier).stop(); + final miningState = ref.read(miningControllerProvider); if (!mounted) { return; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - stopped ? l10n.miningMinerStopSuccess : l10n.miningMinerStopFailure, + stopped + ? l10n.miningMinerStopSuccess + : (miningState.error?.isNotEmpty == true + ? '${l10n.miningMinerStopFailure}: ${miningState.error}' + : l10n.miningMinerStopFailure), ), ), ); @@ -449,6 +459,7 @@ class _MiningScreenState extends ConsumerState { Future _restartMining() async { final l10n = context.l10n; final restarted = await ref.read(miningControllerProvider.notifier).restart(); + final miningState = ref.read(miningControllerProvider); if (!mounted) { return; } @@ -457,7 +468,9 @@ class _MiningScreenState extends ConsumerState { content: Text( restarted ? l10n.miningMinerRestartSuccess - : l10n.miningMinerRestartFailure, + : (miningState.error?.isNotEmpty == true + ? '${l10n.miningMinerRestartFailure}: ${miningState.error}' + : l10n.miningMinerRestartFailure), ), ), );