cm_file_model.dart 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import 'dart:collection';
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_hbb/common.dart';
  5. import 'package:flutter_hbb/models/model.dart';
  6. import 'package:flutter_hbb/models/server_model.dart';
  7. import 'package:get/get.dart';
  8. import 'file_model.dart';
  9. class CmFileModel {
  10. final WeakReference<FFI> parent;
  11. final currentJobTable = RxList<CmFileLog>();
  12. final _jobTables = HashMap<int, RxList<CmFileLog>>.fromEntries([]);
  13. Stopwatch stopwatch = Stopwatch();
  14. int _lastElapsed = 0;
  15. CmFileModel(this.parent);
  16. void updateCurrentClientId(int id) {
  17. if (_jobTables[id] == null) {
  18. _jobTables[id] = RxList<CmFileLog>();
  19. }
  20. Future.delayed(Duration.zero, () {
  21. currentJobTable.value = _jobTables[id]!;
  22. });
  23. }
  24. onFileTransferLog(Map<String, dynamic> evt) {
  25. if (evt['transfer'] != null) {
  26. _onFileTransfer(evt['transfer']);
  27. } else if (evt['remove'] != null) {
  28. _onFileRemove(evt['remove']);
  29. } else if (evt['create_dir'] != null) {
  30. _onDirCreate(evt['create_dir']);
  31. } else if (evt['rename'] != null) {
  32. _onRename(evt['rename']);
  33. }
  34. }
  35. _onFileTransfer(dynamic log) {
  36. try {
  37. dynamic d = jsonDecode(log);
  38. if (!stopwatch.isRunning) stopwatch.start();
  39. bool calcSpeed = stopwatch.elapsedMilliseconds - _lastElapsed >= 1000;
  40. if (calcSpeed) {
  41. _lastElapsed = stopwatch.elapsedMilliseconds;
  42. }
  43. if (d is List<dynamic>) {
  44. for (var l in d) {
  45. _dealOneJob(l, calcSpeed);
  46. }
  47. } else {
  48. _dealOneJob(d, calcSpeed);
  49. }
  50. currentJobTable.refresh();
  51. } catch (e) {
  52. debugPrint("onFileTransferLog:$e");
  53. }
  54. }
  55. _dealOneJob(dynamic l, bool calcSpeed) {
  56. final data = TransferJobSerdeData.fromJson(l);
  57. var jobTable = _jobTables[data.connId];
  58. if (jobTable == null) {
  59. debugPrint("jobTable should not be null");
  60. return;
  61. }
  62. CmFileLog? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
  63. if (job == null) {
  64. job = CmFileLog();
  65. jobTable.add(job);
  66. _addUnread(data.connId);
  67. }
  68. job.id = data.id;
  69. job.action =
  70. data.isRemote ? CmFileAction.remoteToLocal : CmFileAction.localToRemote;
  71. job.fileName = data.path;
  72. job.totalSize = data.totalSize;
  73. job.finishedSize = data.finishedSize;
  74. if (job.finishedSize > data.totalSize) {
  75. job.finishedSize = data.totalSize;
  76. }
  77. if (job.finishedSize > 0) {
  78. if (job.finishedSize < job.totalSize) {
  79. job.state = JobState.inProgress;
  80. } else {
  81. job.state = JobState.done;
  82. }
  83. }
  84. if (data.done) {
  85. job.state = JobState.done;
  86. } else if (data.cancel || data.error == 'skipped') {
  87. job.state = JobState.done;
  88. job.err = 'skipped';
  89. } else if (data.error.isNotEmpty) {
  90. job.state = JobState.error;
  91. job.err = data.error;
  92. }
  93. if (calcSpeed) {
  94. job.speed = (data.transferred - job.lastTransferredSize) * 1.0;
  95. job.lastTransferredSize = data.transferred;
  96. }
  97. jobTable.refresh();
  98. }
  99. _onFileRemove(dynamic log) {
  100. try {
  101. dynamic d = jsonDecode(log);
  102. FileActionLog data = FileActionLog.fromJson(d);
  103. Client? client =
  104. gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == data.connId);
  105. var jobTable = _jobTables[data.connId];
  106. if (jobTable == null) {
  107. debugPrint("jobTable should not be null");
  108. return;
  109. }
  110. int removeUnreadCount = 0;
  111. if (data.dir) {
  112. bool isChild(String parent, String child) {
  113. if (child.startsWith(parent) && child.length > parent.length) {
  114. final suffix = child.substring(parent.length);
  115. return suffix.startsWith('/') || suffix.startsWith('\\');
  116. }
  117. return false;
  118. }
  119. removeUnreadCount = jobTable
  120. .where((e) =>
  121. e.action == CmFileAction.remove &&
  122. isChild(data.path, e.fileName))
  123. .length;
  124. jobTable.removeWhere((e) =>
  125. e.action == CmFileAction.remove && isChild(data.path, e.fileName));
  126. }
  127. jobTable.add(CmFileLog()
  128. ..id = data.id
  129. ..fileName = data.path
  130. ..action = CmFileAction.remove
  131. ..state = JobState.done);
  132. final currentSelectedTab =
  133. gFFI.serverModel.tabController.state.value.selectedTabInfo;
  134. if (!(gFFI.chatModel.isShowCMSidePage &&
  135. currentSelectedTab.key == data.connId.toString())) {
  136. // Wrong number if unreadCount changes during deletion, which rarely happens
  137. RxInt? rx = client?.unreadChatMessageCount;
  138. if (rx != null) {
  139. if (rx.value >= removeUnreadCount) {
  140. rx.value -= removeUnreadCount;
  141. }
  142. rx.value += 1;
  143. }
  144. }
  145. jobTable.refresh();
  146. } catch (e) {
  147. debugPrint('$e');
  148. }
  149. }
  150. _onDirCreate(dynamic log) {
  151. try {
  152. dynamic d = jsonDecode(log);
  153. FileActionLog data = FileActionLog.fromJson(d);
  154. var jobTable = _jobTables[data.connId];
  155. if (jobTable == null) {
  156. debugPrint("jobTable should not be null");
  157. return;
  158. }
  159. jobTable.add(CmFileLog()
  160. ..id = data.id
  161. ..fileName = data.path
  162. ..action = CmFileAction.createDir
  163. ..state = JobState.done);
  164. _addUnread(data.connId);
  165. jobTable.refresh();
  166. } catch (e) {
  167. debugPrint('$e');
  168. }
  169. }
  170. _onRename(dynamic log) {
  171. try {
  172. dynamic d = jsonDecode(log);
  173. FileRenamenLog data = FileRenamenLog.fromJson(d);
  174. var jobTable = _jobTables[data.connId];
  175. if (jobTable == null) {
  176. debugPrint("jobTable should not be null");
  177. return;
  178. }
  179. final fileName = '${data.path} -> ${data.newName}';
  180. jobTable.add(CmFileLog()
  181. ..id = 0
  182. ..fileName = fileName
  183. ..action = CmFileAction.rename
  184. ..state = JobState.done);
  185. _addUnread(data.connId);
  186. jobTable.refresh();
  187. } catch (e) {
  188. debugPrint('$e');
  189. }
  190. }
  191. _addUnread(int connId) {
  192. Client? client =
  193. gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == connId);
  194. final currentSelectedTab =
  195. gFFI.serverModel.tabController.state.value.selectedTabInfo;
  196. if (!(gFFI.chatModel.isShowCMSidePage &&
  197. currentSelectedTab.key == connId.toString())) {
  198. client?.unreadChatMessageCount.value += 1;
  199. }
  200. }
  201. }
  202. enum CmFileAction {
  203. none,
  204. remoteToLocal,
  205. localToRemote,
  206. remove,
  207. createDir,
  208. rename,
  209. }
  210. class CmFileLog {
  211. JobState state = JobState.none;
  212. var id = 0;
  213. var speed = 0.0;
  214. var finishedSize = 0;
  215. var totalSize = 0;
  216. CmFileAction action = CmFileAction.none;
  217. var fileName = "";
  218. var err = "";
  219. int lastTransferredSize = 0;
  220. String display() {
  221. if (state == JobState.done && err == "skipped") {
  222. return translate("Skipped");
  223. }
  224. return state.display();
  225. }
  226. bool isTransfer() {
  227. return action == CmFileAction.remoteToLocal ||
  228. action == CmFileAction.localToRemote;
  229. }
  230. }
  231. class TransferJobSerdeData {
  232. int connId;
  233. int id;
  234. String path;
  235. bool isRemote;
  236. int totalSize;
  237. int finishedSize;
  238. int transferred;
  239. bool done;
  240. bool cancel;
  241. String error;
  242. TransferJobSerdeData({
  243. required this.connId,
  244. required this.id,
  245. required this.path,
  246. required this.isRemote,
  247. required this.totalSize,
  248. required this.finishedSize,
  249. required this.transferred,
  250. required this.done,
  251. required this.cancel,
  252. required this.error,
  253. });
  254. TransferJobSerdeData.fromJson(dynamic d)
  255. : this(
  256. connId: d['connId'] ?? 0,
  257. id: int.tryParse(d['id'].toString()) ?? 0,
  258. path: d['path'] ?? '',
  259. isRemote: d['isRemote'] ?? false,
  260. totalSize: d['totalSize'] ?? 0,
  261. finishedSize: d['finishedSize'] ?? 0,
  262. transferred: d['transferred'] ?? 0,
  263. done: d['done'] ?? false,
  264. cancel: d['cancel'] ?? false,
  265. error: d['error'] ?? '',
  266. );
  267. }
  268. class FileActionLog {
  269. int id = 0;
  270. int connId = 0;
  271. String path = '';
  272. bool dir = false;
  273. FileActionLog({
  274. required this.connId,
  275. required this.id,
  276. required this.path,
  277. required this.dir,
  278. });
  279. FileActionLog.fromJson(dynamic d)
  280. : this(
  281. connId: d['connId'] ?? 0,
  282. id: d['id'] ?? 0,
  283. path: d['path'] ?? '',
  284. dir: d['dir'] ?? false,
  285. );
  286. }
  287. class FileRenamenLog {
  288. int connId = 0;
  289. String path = '';
  290. String newName = '';
  291. FileRenamenLog({
  292. required this.connId,
  293. required this.path,
  294. required this.newName,
  295. });
  296. FileRenamenLog.fromJson(dynamic d)
  297. : this(
  298. connId: d['connId'] ?? 0,
  299. path: d['path'] ?? '',
  300. newName: d['newName'] ?? '',
  301. );
  302. }