329 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| 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['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'] ?? '',
 | |
|         );
 | |
| }
 | |
| 
 | |
| 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'] ?? '',
 | |
|         );
 | |
| }
 |