| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  | 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; | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |   final currentJobTable = RxList<CmFileLog>(); | 
					
						
							|  |  |  |   final _jobTables = HashMap<int, RxList<CmFileLog>>.fromEntries([]); | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |   Stopwatch stopwatch = Stopwatch(); | 
					
						
							|  |  |  |   int _lastElapsed = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   CmFileModel(this.parent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void updateCurrentClientId(int id) { | 
					
						
							|  |  |  |     if (_jobTables[id] == null) { | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |       _jobTables[id] = RxList<CmFileLog>(); | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     Future.delayed(Duration.zero, () { | 
					
						
							|  |  |  |       currentJobTable.value = _jobTables[id]!; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |   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']); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _onFileTransfer(dynamic log) { | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |     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); | 
					
						
							|  |  |  |     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; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |     CmFileLog? job = jobTable.firstWhereOrNull((e) => e.id == data.id); | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |     if (job == null) { | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |       job = CmFileLog(); | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |       jobTable.add(job); | 
					
						
							|  |  |  |       final currentSelectedTab = | 
					
						
							|  |  |  |           gFFI.serverModel.tabController.state.value.selectedTabInfo; | 
					
						
							| 
									
										
										
										
											2023-09-08 09:05:07 +08:00
										 |  |  |       if (!(gFFI.chatModel.isShowCMSidePage && | 
					
						
							|  |  |  |           currentSelectedTab.key == data.connId.toString())) { | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |         client?.unreadChatMessageCount.value += 1; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     job.id = data.id; | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |     job.action = | 
					
						
							|  |  |  |         data.isRemote ? CmFileAction.remoteToLocal : CmFileAction.localToRemote; | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  |     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(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   _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) { | 
					
						
							| 
									
										
										
										
											2023-11-07 10:16:01 +08:00
										 |  |  |         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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |         removeUnreadCount = jobTable | 
					
						
							|  |  |  |             .where((e) => | 
					
						
							|  |  |  |                 e.action == CmFileAction.remove && | 
					
						
							| 
									
										
										
										
											2023-11-07 10:16:01 +08:00
										 |  |  |                 isChild(data.path, e.fileName)) | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |             .length; | 
					
						
							|  |  |  |         jobTable.removeWhere((e) => | 
					
						
							| 
									
										
										
										
											2023-11-07 10:16:01 +08:00
										 |  |  |             e.action == CmFileAction.remove && isChild(data.path, e.fileName)); | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |       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); | 
					
						
							|  |  |  |       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; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       jobTable.add(CmFileLog() | 
					
						
							|  |  |  |         ..id = data.id | 
					
						
							|  |  |  |         ..fileName = data.path | 
					
						
							|  |  |  |         ..action = CmFileAction.createDir | 
					
						
							|  |  |  |         ..state = JobState.done); | 
					
						
							|  |  |  |       final currentSelectedTab = | 
					
						
							|  |  |  |           gFFI.serverModel.tabController.state.value.selectedTabInfo; | 
					
						
							|  |  |  |       if (!(gFFI.chatModel.isShowCMSidePage && | 
					
						
							|  |  |  |           currentSelectedTab.key == data.connId.toString())) { | 
					
						
							|  |  |  |         client?.unreadChatMessageCount.value += 1; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       jobTable.refresh(); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       debugPrint('$e'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum CmFileAction { | 
					
						
							|  |  |  |   none, | 
					
						
							|  |  |  |   remoteToLocal, | 
					
						
							|  |  |  |   localToRemote, | 
					
						
							|  |  |  |   remove, | 
					
						
							|  |  |  |   createDir, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-09-06 16:56:39 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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['path'] ?? '', | 
					
						
							|  |  |  |           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'] ?? '', | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-11-05 15:55:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | } |