fix: Use latest dartssh2 and add a switch for temperature between celsius and millicelsius (#1095)
* refactor(sftp): Optimize file download logic and SSH command execution handling Replace manual chunked downloads with a more concise `sftp.download` method Consistently use `utf8.decode` to process SSH command output Remove redundant code and comments, and simplify the logic * chore: Update `dartssh2` submodule * feat (Temperature Display): Added an option to switch between degrees Celsius and millicelsius Allows users to switch temperature units in server settings, resolving the issue of incorrect temperature display on some devices * chore: Add a participnt * fix(sftp): Fixed a resource leak issue during file downloads and SSH command execution Ensured that remote and local file handles are properly closed during file downloads to prevent resource leaks. Additionally, improved error handling during SSH command execution to ensure that all streams are either successfully completed or properly handled in the event of an error.
This commit is contained in:
@@ -18,6 +18,8 @@ final class ServerCustom {
|
||||
|
||||
final String? preferTempDev;
|
||||
|
||||
final bool tempIsCelsius;
|
||||
|
||||
final String? logoUrl;
|
||||
|
||||
/// The device name of the network interface displayed in the home server card.
|
||||
@@ -33,6 +35,7 @@ final class ServerCustom {
|
||||
this.pvePwd,
|
||||
this.cmds,
|
||||
this.preferTempDev,
|
||||
this.tempIsCelsius = false,
|
||||
this.logoUrl,
|
||||
this.netDev,
|
||||
this.scriptDir,
|
||||
@@ -51,6 +54,7 @@ final class ServerCustom {
|
||||
other.pvePwd == pvePwd &&
|
||||
other.cmds == cmds &&
|
||||
other.preferTempDev == preferTempDev &&
|
||||
other.tempIsCelsius == tempIsCelsius &&
|
||||
other.logoUrl == logoUrl &&
|
||||
other.netDev == netDev &&
|
||||
other.scriptDir == scriptDir;
|
||||
@@ -64,6 +68,7 @@ final class ServerCustom {
|
||||
pvePwd.hashCode ^
|
||||
cmds.hashCode ^
|
||||
preferTempDev.hashCode ^
|
||||
tempIsCelsius.hashCode ^
|
||||
logoUrl.hashCode ^
|
||||
netDev.hashCode ^
|
||||
scriptDir.hashCode;
|
||||
|
||||
@@ -14,6 +14,7 @@ ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
preferTempDev: json['preferTempDev'] as String?,
|
||||
tempIsCelsius: json['tempIsCelsius'] as bool? ?? false,
|
||||
logoUrl: json['logoUrl'] as String?,
|
||||
netDev: json['netDev'] as String?,
|
||||
scriptDir: json['scriptDir'] as String?,
|
||||
@@ -26,6 +27,7 @@ Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
|
||||
'pvePwd': ?instance.pvePwd,
|
||||
'cmds': ?instance.cmds,
|
||||
'preferTempDev': ?instance.preferTempDev,
|
||||
'tempIsCelsius': instance.tempIsCelsius,
|
||||
'logoUrl': ?instance.logoUrl,
|
||||
'netDev': ?instance.netDev,
|
||||
'scriptDir': ?instance.scriptDir,
|
||||
|
||||
@@ -23,12 +23,14 @@ class ServerStatusUpdateReq {
|
||||
final Map<String, String> parsedOutput;
|
||||
final SystemType system;
|
||||
final Map<String, String> customCmds;
|
||||
final double tempDivisor;
|
||||
|
||||
const ServerStatusUpdateReq({
|
||||
required this.system,
|
||||
required this.ss,
|
||||
required this.parsedOutput,
|
||||
required this.customCmds,
|
||||
this.tempDivisor = 1000.0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,6 +90,7 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
req.ss.temps.parse(
|
||||
StatusCmdType.tempType.findInMap(parsedOutput),
|
||||
StatusCmdType.tempVal.findInMap(parsedOutput),
|
||||
divisor: req.tempDivisor,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
@@ -495,7 +498,7 @@ void _parseWindowsTemperatureData(ServerStatusUpdateReq req, Map<String, String>
|
||||
try {
|
||||
final tempRaw = WindowsStatusCmdType.temp.findInMap(parsedOutput);
|
||||
if (tempRaw.isNotEmpty && tempRaw != 'null') {
|
||||
_parseWindowsTemperatures(req.ss.temps, tempRaw);
|
||||
_parseWindowsTemperatures(req.ss.temps, tempRaw, divisor: req.tempDivisor);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Windows temperature parsing failed: $e', s);
|
||||
@@ -648,7 +651,7 @@ List<DiskIOPiece> _parseWindowsDiskIO(String raw, int currentTime) {
|
||||
}
|
||||
}
|
||||
|
||||
void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
||||
void _parseWindowsTemperatures(Temperatures temps, String raw, {double divisor = 1000.0}) {
|
||||
try {
|
||||
// Handle error output
|
||||
if (raw.contains('Error') || raw.contains('Exception') || raw.contains('The term')) {
|
||||
@@ -677,7 +680,7 @@ void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
||||
}
|
||||
|
||||
if (typeLines.isNotEmpty && valueLines.isNotEmpty) {
|
||||
temps.parse(typeLines.join('\n'), valueLines.join('\n'));
|
||||
temps.parse(typeLines.join('\n'), valueLines.join('\n'), divisor: divisor);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to parse Windows temperature data', e, s);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Temperatures {
|
||||
final Map<String, double> _map = {};
|
||||
|
||||
void parse(String type, String value) {
|
||||
void parse(String type, String value, {double divisor = 1000.0}) {
|
||||
final typeSplited = type.split('\n');
|
||||
final valueSplited = value.split('\n');
|
||||
for (var i = 0; i < typeSplited.length && i < valueSplited.length; i++) {
|
||||
@@ -15,7 +15,7 @@ class Temperatures {
|
||||
if (temp == null) {
|
||||
continue;
|
||||
}
|
||||
_map[name] = temp / 1000;
|
||||
_map[name] = temp / divisor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,48 +84,52 @@ Future<void> _download(
|
||||
);
|
||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
||||
|
||||
/// Create the directory if not exists
|
||||
final dirPath = req.localPath.substring(
|
||||
0,
|
||||
req.localPath.lastIndexOf(Pfs.seperator),
|
||||
);
|
||||
await Directory(dirPath).create(recursive: true);
|
||||
|
||||
/// Use [FileMode.write] to overwrite the file
|
||||
final localFile = File(req.localPath).openWrite(mode: FileMode.write);
|
||||
final file = await (await client.sftp()).open(req.remotePath);
|
||||
final size = (await file.stat()).size;
|
||||
if (size == null) {
|
||||
mainSendPort.send(Exception('can\'t get file size: ${req.remotePath}'));
|
||||
return;
|
||||
final sftp = await client.sftp();
|
||||
|
||||
final remoteFile = await sftp.open(req.remotePath);
|
||||
int? size;
|
||||
try {
|
||||
size = (await remoteFile.stat()).size;
|
||||
if (size == null) {
|
||||
mainSendPort.send(Exception('can\'t get file size: ${req.remotePath}'));
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
await remoteFile.close();
|
||||
}
|
||||
|
||||
mainSendPort.send(size);
|
||||
mainSendPort.send(SftpWorkerStatus.loading);
|
||||
|
||||
// Issue #161
|
||||
// Due to single core performance, limit the chunk size
|
||||
const defaultChunkSize = 1024 * 1024 * 5;
|
||||
var totalRead = 0;
|
||||
const chunkSize = 1024 * 1024 * 5;
|
||||
var lastProgress = 0;
|
||||
final localFile = File(req.localPath).openWrite(mode: FileMode.write);
|
||||
|
||||
while (totalRead < size) {
|
||||
final remaining = size - totalRead;
|
||||
final chunkSize = remaining > defaultChunkSize
|
||||
? defaultChunkSize
|
||||
: remaining;
|
||||
dprint('Size: $size, Total Read: $totalRead, Chunk Size: $chunkSize');
|
||||
|
||||
final fileData = file.read(offset: totalRead, length: chunkSize);
|
||||
await for (var chunk in fileData) {
|
||||
localFile.add(chunk);
|
||||
totalRead += chunk.length;
|
||||
mainSendPort.send(totalRead / size * 100);
|
||||
}
|
||||
try {
|
||||
await sftp.download(
|
||||
req.remotePath,
|
||||
localFile,
|
||||
onProgress: (bytesRead) {
|
||||
final s = size;
|
||||
if (s == null || s == 0) return;
|
||||
final progress = (bytesRead / s * 100).round();
|
||||
if (progress != lastProgress) {
|
||||
lastProgress = progress;
|
||||
mainSendPort.send(progress);
|
||||
}
|
||||
},
|
||||
chunkSize: chunkSize,
|
||||
);
|
||||
} finally {
|
||||
await localFile.close();
|
||||
}
|
||||
|
||||
await localFile.close();
|
||||
await file.close();
|
||||
|
||||
mainSendPort.send(watch.elapsed);
|
||||
mainSendPort.send(SftpWorkerStatus.finished);
|
||||
} catch (e) {
|
||||
|
||||
@@ -42,7 +42,7 @@ final class PrivateKeyNotifierProvider
|
||||
}
|
||||
|
||||
String _$privateKeyNotifierHash() =>
|
||||
r'12edd05dca29d1cbc9e2a3e047c3d417d22f7bb7';
|
||||
r'79d02e116fe665df1ccb0719590947e109a5a736';
|
||||
|
||||
abstract class _$PrivateKeyNotifier extends $Notifier<PrivateKeyState> {
|
||||
PrivateKeyState build();
|
||||
|
||||
@@ -41,7 +41,7 @@ final class ServersNotifierProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$serversNotifierHash() => r'c90c2d8ce73a63f926bcf9679a84ae150c9d4808';
|
||||
String _$serversNotifierHash() => r'29a4cb286b7032e5e74841f4eba66941b0dec34e';
|
||||
|
||||
abstract class _$ServersNotifier extends $Notifier<ServersState> {
|
||||
ServersState build();
|
||||
|
||||
@@ -361,6 +361,7 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
parsedOutput: parsedOutput,
|
||||
system: state.status.system,
|
||||
customCmds: spi.custom?.cmds ?? {},
|
||||
tempDivisor: spi.custom?.tempIsCelsius == true ? 1.0 : 1000.0,
|
||||
);
|
||||
final newStatus = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${spi.id}>');
|
||||
updateStatus(newStatus);
|
||||
|
||||
@@ -58,7 +58,7 @@ final class ServerNotifierProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$serverNotifierHash() => r'04b1beef4d96242fd10d5b523c6f5f17eb774bae';
|
||||
String _$serverNotifierHash() => r'bf724a58c5d0ec99f4e00ebe7cea47a7b6b2cc64';
|
||||
|
||||
final class ServerNotifierFamily extends $Family
|
||||
with
|
||||
|
||||
@@ -41,7 +41,7 @@ final class SnippetNotifierProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$snippetNotifierHash() => r'8285c7edf905a4aaa41cd8b65b0a6755c8b97fc9';
|
||||
String _$snippetNotifierHash() => r'46297b84ec6497e5c454be6ffe32330b37c6a465';
|
||||
|
||||
abstract class _$SnippetNotifier extends $Notifier<SnippetState> {
|
||||
SnippetState build();
|
||||
|
||||
@@ -156,7 +156,8 @@ abstract final class GithubIds {
|
||||
'xxnuo',
|
||||
'sunnysu0608',
|
||||
'Staten-Wang',
|
||||
'alterkeyy'
|
||||
'alterkeyy',
|
||||
'zhbyu',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user