Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'file_model.dart';
|
||||
|
||||
class CmFileModel {
|
||||
final WeakReference<FFI> parent;
|
||||
final currentJobTable = RxList<CmFileLog>();
|
||||
final _jobTables = HashMap<int, RxList<CmFileLog>>.fromEntries([]);
|
||||
Stopwatch stopwatch = Stopwatch();
|
||||
int _lastElapsed = 0;
|
||||
|
||||
CmFileModel(this.parent);
|
||||
|
||||
void updateCurrentClientId(int id) {
|
||||
if (_jobTables[id] == null) {
|
||||
_jobTables[id] = RxList<CmFileLog>();
|
||||
}
|
||||
Future.delayed(Duration.zero, () {
|
||||
currentJobTable.value = _jobTables[id]!;
|
||||
});
|
||||
}
|
||||
|
||||
onFileTransferLog(Map<String, dynamic> evt) {
|
||||
if (evt['transfer'] != null) {
|
||||
_onFileTransfer(evt['transfer']);
|
||||
} else if (evt['remove'] != null) {
|
||||
_onFileRemove(evt['remove']);
|
||||
} else if (evt['create_dir'] != null) {
|
||||
_onDirCreate(evt['create_dir']);
|
||||
} else if (evt['rename'] != null) {
|
||||
_onRename(evt['rename']);
|
||||
}
|
||||
}
|
||||
|
||||
_onFileTransfer(dynamic log) {
|
||||
try {
|
||||
dynamic d = jsonDecode(log);
|
||||
if (!stopwatch.isRunning) stopwatch.start();
|
||||
bool calcSpeed = stopwatch.elapsedMilliseconds - _lastElapsed >= 1000;
|
||||
if (calcSpeed) {
|
||||
_lastElapsed = stopwatch.elapsedMilliseconds;
|
||||
}
|
||||
if (d is List<dynamic>) {
|
||||
for (var l in d) {
|
||||
_dealOneJob(l, calcSpeed);
|
||||
}
|
||||
} else {
|
||||
_dealOneJob(d, calcSpeed);
|
||||
}
|
||||
currentJobTable.refresh();
|
||||
} catch (e) {
|
||||
debugPrint("onFileTransferLog:$e");
|
||||
}
|
||||
}
|
||||
|
||||
_dealOneJob(dynamic l, bool calcSpeed) {
|
||||
final data = TransferJobSerdeData.fromJson(l);
|
||||
var jobTable = _jobTables[data.connId];
|
||||
if (jobTable == null) {
|
||||
debugPrint("jobTable should not be null");
|
||||
return;
|
||||
}
|
||||
CmFileLog? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
|
||||
if (job == null) {
|
||||
job = CmFileLog();
|
||||
jobTable.add(job);
|
||||
_addUnread(data.connId);
|
||||
}
|
||||
job.id = data.id;
|
||||
job.action =
|
||||
data.isRemote ? CmFileAction.remoteToLocal : CmFileAction.localToRemote;
|
||||
job.fileName = data.path;
|
||||
job.totalSize = data.totalSize;
|
||||
job.finishedSize = data.finishedSize;
|
||||
if (job.finishedSize > data.totalSize) {
|
||||
job.finishedSize = data.totalSize;
|
||||
}
|
||||
|
||||
if (job.finishedSize > 0) {
|
||||
if (job.finishedSize < job.totalSize) {
|
||||
job.state = JobState.inProgress;
|
||||
} else {
|
||||
job.state = JobState.done;
|
||||
}
|
||||
}
|
||||
if (data.done) {
|
||||
job.state = JobState.done;
|
||||
} else if (data.cancel || data.error == 'skipped') {
|
||||
job.state = JobState.done;
|
||||
job.err = 'skipped';
|
||||
} else if (data.error.isNotEmpty) {
|
||||
job.state = JobState.error;
|
||||
job.err = data.error;
|
||||
}
|
||||
if (calcSpeed) {
|
||||
job.speed = (data.transferred - job.lastTransferredSize) * 1.0;
|
||||
job.lastTransferredSize = data.transferred;
|
||||
}
|
||||
jobTable.refresh();
|
||||
}
|
||||
|
||||
_onFileRemove(dynamic log) {
|
||||
try {
|
||||
dynamic d = jsonDecode(log);
|
||||
FileActionLog data = FileActionLog.fromJson(d);
|
||||
Client? client =
|
||||
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == data.connId);
|
||||
var jobTable = _jobTables[data.connId];
|
||||
if (jobTable == null) {
|
||||
debugPrint("jobTable should not be null");
|
||||
return;
|
||||
}
|
||||
int removeUnreadCount = 0;
|
||||
if (data.dir) {
|
||||
bool isChild(String parent, String child) {
|
||||
if (child.startsWith(parent) && child.length > parent.length) {
|
||||
final suffix = child.substring(parent.length);
|
||||
return suffix.startsWith('/') || suffix.startsWith('\\');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeUnreadCount = jobTable
|
||||
.where((e) =>
|
||||
e.action == CmFileAction.remove &&
|
||||
isChild(data.path, e.fileName))
|
||||
.length;
|
||||
jobTable.removeWhere((e) =>
|
||||
e.action == CmFileAction.remove && isChild(data.path, e.fileName));
|
||||
}
|
||||
jobTable.add(CmFileLog()
|
||||
..id = data.id
|
||||
..fileName = data.path
|
||||
..action = CmFileAction.remove
|
||||
..state = JobState.done);
|
||||
final currentSelectedTab =
|
||||
gFFI.serverModel.tabController.state.value.selectedTabInfo;
|
||||
if (!(gFFI.chatModel.isShowCMSidePage &&
|
||||
currentSelectedTab.key == data.connId.toString())) {
|
||||
// Wrong number if unreadCount changes during deletion, which rarely happens
|
||||
RxInt? rx = client?.unreadChatMessageCount;
|
||||
if (rx != null) {
|
||||
if (rx.value >= removeUnreadCount) {
|
||||
rx.value -= removeUnreadCount;
|
||||
}
|
||||
rx.value += 1;
|
||||
}
|
||||
}
|
||||
jobTable.refresh();
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
|
||||
_onDirCreate(dynamic log) {
|
||||
try {
|
||||
dynamic d = jsonDecode(log);
|
||||
FileActionLog data = FileActionLog.fromJson(d);
|
||||
var jobTable = _jobTables[data.connId];
|
||||
if (jobTable == null) {
|
||||
debugPrint("jobTable should not be null");
|
||||
return;
|
||||
}
|
||||
jobTable.add(CmFileLog()
|
||||
..id = data.id
|
||||
..fileName = data.path
|
||||
..action = CmFileAction.createDir
|
||||
..state = JobState.done);
|
||||
_addUnread(data.connId);
|
||||
jobTable.refresh();
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
|
||||
_onRename(dynamic log) {
|
||||
try {
|
||||
dynamic d = jsonDecode(log);
|
||||
FileRenamenLog data = FileRenamenLog.fromJson(d);
|
||||
var jobTable = _jobTables[data.connId];
|
||||
if (jobTable == null) {
|
||||
debugPrint("jobTable should not be null");
|
||||
return;
|
||||
}
|
||||
final fileName = '${data.path} -> ${data.newName}';
|
||||
jobTable.add(CmFileLog()
|
||||
..id = 0
|
||||
..fileName = fileName
|
||||
..action = CmFileAction.rename
|
||||
..state = JobState.done);
|
||||
_addUnread(data.connId);
|
||||
jobTable.refresh();
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
|
||||
_addUnread(int connId) {
|
||||
Client? client =
|
||||
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == connId);
|
||||
final currentSelectedTab =
|
||||
gFFI.serverModel.tabController.state.value.selectedTabInfo;
|
||||
if (!(gFFI.chatModel.isShowCMSidePage &&
|
||||
currentSelectedTab.key == connId.toString())) {
|
||||
client?.unreadChatMessageCount.value += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CmFileAction {
|
||||
none,
|
||||
remoteToLocal,
|
||||
localToRemote,
|
||||
remove,
|
||||
createDir,
|
||||
rename,
|
||||
}
|
||||
|
||||
class CmFileLog {
|
||||
JobState state = JobState.none;
|
||||
var id = 0;
|
||||
var speed = 0.0;
|
||||
var finishedSize = 0;
|
||||
var totalSize = 0;
|
||||
CmFileAction action = CmFileAction.none;
|
||||
var fileName = "";
|
||||
var err = "";
|
||||
int lastTransferredSize = 0;
|
||||
|
||||
String display() {
|
||||
if (state == JobState.done && err == "skipped") {
|
||||
return translate("Skipped");
|
||||
}
|
||||
return state.display();
|
||||
}
|
||||
|
||||
bool isTransfer() {
|
||||
return action == CmFileAction.remoteToLocal ||
|
||||
action == CmFileAction.localToRemote;
|
||||
}
|
||||
}
|
||||
|
||||
class TransferJobSerdeData {
|
||||
int connId;
|
||||
int id;
|
||||
String path;
|
||||
bool isRemote;
|
||||
int totalSize;
|
||||
int finishedSize;
|
||||
int transferred;
|
||||
bool done;
|
||||
bool cancel;
|
||||
String error;
|
||||
|
||||
TransferJobSerdeData({
|
||||
required this.connId,
|
||||
required this.id,
|
||||
required this.path,
|
||||
required this.isRemote,
|
||||
required this.totalSize,
|
||||
required this.finishedSize,
|
||||
required this.transferred,
|
||||
required this.done,
|
||||
required this.cancel,
|
||||
required this.error,
|
||||
});
|
||||
|
||||
TransferJobSerdeData.fromJson(dynamic d)
|
||||
: this(
|
||||
connId: d['connId'] ?? 0,
|
||||
id: int.tryParse(d['id'].toString()) ?? 0,
|
||||
path: d['dataSource'] ?? '',
|
||||
isRemote: d['isRemote'] ?? false,
|
||||
totalSize: d['totalSize'] ?? 0,
|
||||
finishedSize: d['finishedSize'] ?? 0,
|
||||
transferred: d['transferred'] ?? 0,
|
||||
done: d['done'] ?? false,
|
||||
cancel: d['cancel'] ?? false,
|
||||
error: d['error'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
class FileActionLog {
|
||||
int id = 0;
|
||||
int connId = 0;
|
||||
String path = '';
|
||||
bool dir = false;
|
||||
|
||||
FileActionLog({
|
||||
required this.connId,
|
||||
required this.id,
|
||||
required this.path,
|
||||
required this.dir,
|
||||
});
|
||||
|
||||
FileActionLog.fromJson(dynamic d)
|
||||
: this(
|
||||
connId: d['connId'] ?? 0,
|
||||
id: d['id'] ?? 0,
|
||||
path: d['path'] ?? '',
|
||||
dir: d['dir'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
class FileRenamenLog {
|
||||
int connId = 0;
|
||||
String path = '';
|
||||
String newName = '';
|
||||
|
||||
FileRenamenLog({
|
||||
required this.connId,
|
||||
required this.path,
|
||||
required this.newName,
|
||||
});
|
||||
|
||||
FileRenamenLog.fromJson(dynamic d)
|
||||
: this(
|
||||
connId: d['connId'] ?? 0,
|
||||
path: d['path'] ?? '',
|
||||
newName: d['newName'] ?? '',
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user