cm file delete/create log
Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
27112e3480
commit
663d355a48
@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
import 'package:flutter_hbb/models/chat_model.dart';
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/cm_file_model.dart';
|
||||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||||
@ -482,8 +483,8 @@ class _CmHeaderState extends State<_CmHeader>
|
|||||||
client.type_() != ClientType.file),
|
client.type_() != ClientType.file),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () => checkClickTime(client.id, () {
|
onPressed: () => checkClickTime(client.id, () {
|
||||||
if (client.type_() != ClientType.file) {
|
if (client.type_() == ClientType.file) {
|
||||||
gFFI.chatModel.toggleCMSidePage();
|
gFFI.chatModel.toggleCMFilePage();
|
||||||
} else {
|
} else {
|
||||||
gFFI.chatModel
|
gFFI.chatModel
|
||||||
.toggleCMChatPage(MessageKey(client.peerId, client.id));
|
.toggleCMChatPage(MessageKey(client.peerId, client.id));
|
||||||
@ -975,6 +976,49 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iconLabel(CmFileLog item) {
|
||||||
|
switch (item.action) {
|
||||||
|
case CmFileAction.none:
|
||||||
|
return Container();
|
||||||
|
case CmFileAction.localToRemote:
|
||||||
|
case CmFileAction.remoteToLocal:
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Transform.rotate(
|
||||||
|
angle: item.action == CmFileAction.remoteToLocal ? 0 : pi,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
"assets/arrow.svg",
|
||||||
|
color: Theme.of(context).tabBarTheme.labelColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(item.action == CmFileAction.remoteToLocal
|
||||||
|
? translate('Send')
|
||||||
|
: translate('Receive'))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case CmFileAction.remove:
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Theme.of(context).tabBarTheme.labelColor,
|
||||||
|
),
|
||||||
|
Text(translate('Delete'))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case CmFileAction.createDir:
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.create_new_folder,
|
||||||
|
color: Theme.of(context).tabBarTheme.labelColor,
|
||||||
|
),
|
||||||
|
Text(translate('Create Folder'))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget statusList() {
|
Widget statusList() {
|
||||||
return PreferredSize(
|
return PreferredSize(
|
||||||
preferredSize: const Size(200, double.infinity),
|
preferredSize: const Size(200, double.infinity),
|
||||||
@ -983,7 +1027,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
|
|||||||
child: Obx(
|
child: Obx(
|
||||||
() {
|
() {
|
||||||
final jobTable = gFFI.cmFileModel.currentJobTable;
|
final jobTable = gFFI.cmFileModel.currentJobTable;
|
||||||
statusListView(List<JobProgress> jobs) => ListView.builder(
|
statusListView(List<CmFileLog> jobs) => ListView.builder(
|
||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final item = jobs[index];
|
final item = jobs[index];
|
||||||
@ -998,22 +1042,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 50,
|
width: 50,
|
||||||
child: Column(
|
child: iconLabel(item),
|
||||||
children: [
|
|
||||||
Transform.rotate(
|
|
||||||
angle: item.isRemoteToLocal ? 0 : pi,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
"assets/arrow.svg",
|
|
||||||
color: Theme.of(context)
|
|
||||||
.tabBarTheme
|
|
||||||
.labelColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(item.isRemoteToLocal
|
|
||||||
? translate('Send')
|
|
||||||
: translate('Receive'))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).paddingOnly(left: 15),
|
).paddingOnly(left: 15),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16.0,
|
width: 16.0,
|
||||||
@ -1048,8 +1077,9 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage:
|
offstage: !(item.isTransfer() &&
|
||||||
item.state == JobState.inProgress,
|
item.state !=
|
||||||
|
JobState.inProgress),
|
||||||
child: Text(
|
child: Text(
|
||||||
translate(
|
translate(
|
||||||
item.display(),
|
item.display(),
|
||||||
|
@ -285,6 +285,10 @@ class ChatModel with ChangeNotifier {
|
|||||||
await toggleCMSidePage();
|
await toggleCMSidePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCMFilePage() async {
|
||||||
|
await toggleCMSidePage();
|
||||||
|
}
|
||||||
|
|
||||||
var _togglingCMSidePage = false; // protect order for await
|
var _togglingCMSidePage = false; // protect order for await
|
||||||
toggleCMSidePage() async {
|
toggleCMSidePage() async {
|
||||||
if (_togglingCMSidePage) return false;
|
if (_togglingCMSidePage) return false;
|
||||||
@ -296,6 +300,13 @@ class ChatModel with ChangeNotifier {
|
|||||||
await windowManager.setSizeAlignment(
|
await windowManager.setSizeAlignment(
|
||||||
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
|
||||||
} else {
|
} else {
|
||||||
|
final currentSelectedTab =
|
||||||
|
gFFI.serverModel.tabController.state.value.selectedTabInfo;
|
||||||
|
final client = parent.target?.serverModel.clients.firstWhereOrNull(
|
||||||
|
(client) => client.id.toString() == currentSelectedTab.key);
|
||||||
|
if (client != null) {
|
||||||
|
client.unreadChatMessageCount.value = 0;
|
||||||
|
}
|
||||||
requestChatInputFocus();
|
requestChatInputFocus();
|
||||||
await windowManager.show();
|
await windowManager.show();
|
||||||
await windowManager.setSizeAlignment(
|
await windowManager.setSizeAlignment(
|
||||||
|
@ -10,8 +10,8 @@ import 'file_model.dart';
|
|||||||
|
|
||||||
class CmFileModel {
|
class CmFileModel {
|
||||||
final WeakReference<FFI> parent;
|
final WeakReference<FFI> parent;
|
||||||
final currentJobTable = RxList<JobProgress>();
|
final currentJobTable = RxList<CmFileLog>();
|
||||||
final _jobTables = HashMap<int, RxList<JobProgress>>.fromEntries([]);
|
final _jobTables = HashMap<int, RxList<CmFileLog>>.fromEntries([]);
|
||||||
Stopwatch stopwatch = Stopwatch();
|
Stopwatch stopwatch = Stopwatch();
|
||||||
int _lastElapsed = 0;
|
int _lastElapsed = 0;
|
||||||
|
|
||||||
@ -19,14 +19,24 @@ class CmFileModel {
|
|||||||
|
|
||||||
void updateCurrentClientId(int id) {
|
void updateCurrentClientId(int id) {
|
||||||
if (_jobTables[id] == null) {
|
if (_jobTables[id] == null) {
|
||||||
_jobTables[id] = RxList<JobProgress>();
|
_jobTables[id] = RxList<CmFileLog>();
|
||||||
}
|
}
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
currentJobTable.value = _jobTables[id]!;
|
currentJobTable.value = _jobTables[id]!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileTransferLog(dynamic log) {
|
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) {
|
||||||
try {
|
try {
|
||||||
dynamic d = jsonDecode(log);
|
dynamic d = jsonDecode(log);
|
||||||
if (!stopwatch.isRunning) stopwatch.start();
|
if (!stopwatch.isRunning) stopwatch.start();
|
||||||
@ -56,9 +66,9 @@ class CmFileModel {
|
|||||||
debugPrint("jobTable should not be null");
|
debugPrint("jobTable should not be null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JobProgress? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
|
CmFileLog? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
job = JobProgress();
|
job = CmFileLog();
|
||||||
jobTable.add(job);
|
jobTable.add(job);
|
||||||
final currentSelectedTab =
|
final currentSelectedTab =
|
||||||
gFFI.serverModel.tabController.state.value.selectedTabInfo;
|
gFFI.serverModel.tabController.state.value.selectedTabInfo;
|
||||||
@ -68,14 +78,14 @@ class CmFileModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
job.id = data.id;
|
job.id = data.id;
|
||||||
job.isRemoteToLocal = data.isRemote;
|
job.action =
|
||||||
|
data.isRemote ? CmFileAction.remoteToLocal : CmFileAction.localToRemote;
|
||||||
job.fileName = data.path;
|
job.fileName = data.path;
|
||||||
job.totalSize = data.totalSize;
|
job.totalSize = data.totalSize;
|
||||||
job.finishedSize = data.finishedSize;
|
job.finishedSize = data.finishedSize;
|
||||||
if (job.finishedSize > data.totalSize) {
|
if (job.finishedSize > data.totalSize) {
|
||||||
job.finishedSize = data.totalSize;
|
job.finishedSize = data.totalSize;
|
||||||
}
|
}
|
||||||
job.isRemoteToLocal = data.isRemote;
|
|
||||||
|
|
||||||
if (job.finishedSize > 0) {
|
if (job.finishedSize > 0) {
|
||||||
if (job.finishedSize < job.totalSize) {
|
if (job.finishedSize < job.totalSize) {
|
||||||
@ -99,6 +109,112 @@ class CmFileModel {
|
|||||||
}
|
}
|
||||||
jobTable.refresh();
|
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) {
|
||||||
|
removeUnreadCount = jobTable
|
||||||
|
.where((e) =>
|
||||||
|
e.action == CmFileAction.remove &&
|
||||||
|
e.fileName.startsWith(data.path))
|
||||||
|
.length;
|
||||||
|
jobTable.removeWhere((e) =>
|
||||||
|
e.action == CmFileAction.remove &&
|
||||||
|
e.fileName.startsWith(data.path));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransferJobSerdeData {
|
class TransferJobSerdeData {
|
||||||
@ -140,3 +256,25 @@ class TransferJobSerdeData {
|
|||||||
error: d['error'] ?? '',
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -353,7 +353,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
} else if (name == "cm_file_transfer_log") {
|
} else if (name == "cm_file_transfer_log") {
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
gFFI.cmFileModel.onFileTransferLog(evt['log']);
|
gFFI.cmFileModel.onFileTransferLog(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debugPrint('Unknown event name: $name');
|
debugPrint('Unknown event name: $name');
|
||||||
|
@ -1031,8 +1031,8 @@ pub mod connection_manager {
|
|||||||
self.push_event("update_voice_call_state", vec![("client", &client_json)]);
|
self.push_event("update_voice_call_state", vec![("client", &client_json)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_transfer_log(&self, log: String) {
|
fn file_transfer_log(&self, action: &str, log: &str) {
|
||||||
self.push_event("cm_file_transfer_log", vec![("log", &log.to_string())]);
|
self.push_event("cm_file_transfer_log", vec![(action, log)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ pub enum Data {
|
|||||||
Plugin(Plugin),
|
Plugin(Plugin),
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
SyncWinCpuUsage(Option<f64>),
|
SyncWinCpuUsage(Option<f64>),
|
||||||
FileTransferLog(String),
|
FileTransferLog((String, String)),
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
ControlledSessionCount(usize),
|
ControlledSessionCount(usize),
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ use hbb_common::{
|
|||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
use scrap::android::call_main_service_pointer_input;
|
use scrap::android::call_main_service_pointer_input;
|
||||||
|
use serde_derive::Serialize;
|
||||||
use serde_json::{json, value::Value};
|
use serde_json::{json, value::Value};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
@ -223,6 +224,7 @@ pub struct Connection {
|
|||||||
start_cm_ipc_para: Option<StartCmIpcPara>,
|
start_cm_ipc_para: Option<StartCmIpcPara>,
|
||||||
auto_disconnect_timer: Option<(Instant, u64)>,
|
auto_disconnect_timer: Option<(Instant, u64)>,
|
||||||
authed_conn_id: Option<self::raii::AuthedConnID>,
|
authed_conn_id: Option<self::raii::AuthedConnID>,
|
||||||
|
file_remove_log_control: FileRemoveLogControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnInner {
|
impl ConnInner {
|
||||||
@ -365,6 +367,7 @@ impl Connection {
|
|||||||
}),
|
}),
|
||||||
auto_disconnect_timer: None,
|
auto_disconnect_timer: None,
|
||||||
authed_conn_id: None,
|
authed_conn_id: None,
|
||||||
|
file_remove_log_control: FileRemoveLogControl::new(id),
|
||||||
};
|
};
|
||||||
let addr = hbb_common::try_into_v4(addr);
|
let addr = hbb_common::try_into_v4(addr);
|
||||||
if !conn.on_open(addr).await {
|
if !conn.on_open(addr).await {
|
||||||
@ -556,11 +559,11 @@ impl Connection {
|
|||||||
},
|
},
|
||||||
_ = conn.file_timer.tick() => {
|
_ = conn.file_timer.tick() => {
|
||||||
if !conn.read_jobs.is_empty() {
|
if !conn.read_jobs.is_empty() {
|
||||||
conn.send_to_cm(ipc::Data::FileTransferLog(fs::serialize_transfer_jobs(&conn.read_jobs)));
|
conn.send_to_cm(ipc::Data::FileTransferLog(("transfer".to_string(), fs::serialize_transfer_jobs(&conn.read_jobs))));
|
||||||
match fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
|
match fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
|
||||||
Ok(log) => {
|
Ok(log) => {
|
||||||
if !log.is_empty() {
|
if !log.is_empty() {
|
||||||
conn.send_to_cm(ipc::Data::FileTransferLog(log));
|
conn.send_to_cm(ipc::Data::FileTransferLog(("transfer".to_string(), log)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -632,6 +635,7 @@ impl Connection {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
conn.file_remove_log_control.on_timer().drain(..).map(|x| conn.send_to_cm(x)).count();
|
||||||
}
|
}
|
||||||
_ = test_delay_timer.tick() => {
|
_ = test_delay_timer.tick() => {
|
||||||
if last_recv_time.elapsed() >= SEC30 {
|
if last_recv_time.elapsed() >= SEC30 {
|
||||||
@ -1911,30 +1915,43 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
Some(file_action::Union::RemoveDir(d)) => {
|
Some(file_action::Union::RemoveDir(d)) => {
|
||||||
self.send_fs(ipc::FS::RemoveDir {
|
self.send_fs(ipc::FS::RemoveDir {
|
||||||
path: d.path,
|
path: d.path.clone(),
|
||||||
id: d.id,
|
id: d.id,
|
||||||
recursive: d.recursive,
|
recursive: d.recursive,
|
||||||
});
|
});
|
||||||
|
self.file_remove_log_control.on_remove_dir(d);
|
||||||
}
|
}
|
||||||
Some(file_action::Union::RemoveFile(f)) => {
|
Some(file_action::Union::RemoveFile(f)) => {
|
||||||
self.send_fs(ipc::FS::RemoveFile {
|
self.send_fs(ipc::FS::RemoveFile {
|
||||||
path: f.path,
|
path: f.path.clone(),
|
||||||
id: f.id,
|
id: f.id,
|
||||||
file_num: f.file_num,
|
file_num: f.file_num,
|
||||||
});
|
});
|
||||||
|
self.file_remove_log_control.on_remove_file(f);
|
||||||
}
|
}
|
||||||
Some(file_action::Union::Create(c)) => {
|
Some(file_action::Union::Create(c)) => {
|
||||||
self.send_fs(ipc::FS::CreateDir {
|
self.send_fs(ipc::FS::CreateDir {
|
||||||
path: c.path,
|
path: c.path.clone(),
|
||||||
id: c.id,
|
id: c.id,
|
||||||
});
|
});
|
||||||
|
self.send_to_cm(ipc::Data::FileTransferLog((
|
||||||
|
"create_dir".to_string(),
|
||||||
|
serde_json::to_string(&FileActionLog {
|
||||||
|
id: c.id,
|
||||||
|
conn_id: self.inner.id(),
|
||||||
|
path: c.path,
|
||||||
|
dir: true,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Some(file_action::Union::Cancel(c)) => {
|
Some(file_action::Union::Cancel(c)) => {
|
||||||
self.send_fs(ipc::FS::CancelWrite { id: c.id });
|
self.send_fs(ipc::FS::CancelWrite { id: c.id });
|
||||||
if let Some(job) = fs::get_job_immutable(c.id, &self.read_jobs) {
|
if let Some(job) = fs::get_job_immutable(c.id, &self.read_jobs) {
|
||||||
self.send_to_cm(ipc::Data::FileTransferLog(
|
self.send_to_cm(ipc::Data::FileTransferLog((
|
||||||
|
"transfer".to_string(),
|
||||||
fs::serialize_transfer_job(job, false, true, ""),
|
fs::serialize_transfer_job(job, false, true, ""),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
fs::remove_job(c.id, &mut self.read_jobs);
|
fs::remove_job(c.id, &mut self.read_jobs);
|
||||||
}
|
}
|
||||||
@ -2873,6 +2890,109 @@ pub enum FileAuditType {
|
|||||||
RemoteReceive = 1,
|
RemoteReceive = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FileActionLog {
|
||||||
|
id: i32,
|
||||||
|
conn_id: i32,
|
||||||
|
path: String,
|
||||||
|
dir: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileRemoveLogControl {
|
||||||
|
conn_id: i32,
|
||||||
|
instant: Instant,
|
||||||
|
removed_files: Vec<FileRemoveFile>,
|
||||||
|
removed_dirs: Vec<FileRemoveDir>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileRemoveLogControl {
|
||||||
|
fn new(conn_id: i32) -> Self {
|
||||||
|
FileRemoveLogControl {
|
||||||
|
conn_id,
|
||||||
|
instant: Instant::now(),
|
||||||
|
removed_files: vec![],
|
||||||
|
removed_dirs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_remove_file(&mut self, f: FileRemoveFile) -> Option<ipc::Data> {
|
||||||
|
self.instant = Instant::now();
|
||||||
|
self.removed_files.push(f.clone());
|
||||||
|
Some(ipc::Data::FileTransferLog((
|
||||||
|
"remove".to_string(),
|
||||||
|
serde_json::to_string(&FileActionLog {
|
||||||
|
id: f.id,
|
||||||
|
conn_id: self.conn_id,
|
||||||
|
path: f.path,
|
||||||
|
dir: false,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_remove_dir(&mut self, d: FileRemoveDir) -> Option<ipc::Data> {
|
||||||
|
self.instant = Instant::now();
|
||||||
|
self.removed_files.retain(|f| !f.path.starts_with(&d.path));
|
||||||
|
self.removed_dirs.retain(|x| !x.path.starts_with(&d.path));
|
||||||
|
if !self
|
||||||
|
.removed_dirs
|
||||||
|
.iter()
|
||||||
|
.any(|x| d.path.starts_with(&x.path))
|
||||||
|
{
|
||||||
|
self.removed_dirs.push(d.clone());
|
||||||
|
}
|
||||||
|
Some(ipc::Data::FileTransferLog((
|
||||||
|
"remove".to_string(),
|
||||||
|
serde_json::to_string(&FileActionLog {
|
||||||
|
id: d.id,
|
||||||
|
conn_id: self.conn_id,
|
||||||
|
path: d.path,
|
||||||
|
dir: true,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_timer(&mut self) -> Vec<ipc::Data> {
|
||||||
|
if self.instant.elapsed().as_secs() < 1 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let mut v: Vec<ipc::Data> = vec![];
|
||||||
|
self.removed_files
|
||||||
|
.drain(..)
|
||||||
|
.map(|f| {
|
||||||
|
v.push(ipc::Data::FileTransferLog((
|
||||||
|
"remove".to_string(),
|
||||||
|
serde_json::to_string(&FileActionLog {
|
||||||
|
id: f.id,
|
||||||
|
conn_id: self.conn_id,
|
||||||
|
path: f.path,
|
||||||
|
dir: false,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)));
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
self.removed_dirs
|
||||||
|
.drain(..)
|
||||||
|
.map(|d| {
|
||||||
|
v.push(ipc::Data::FileTransferLog((
|
||||||
|
"remove".to_string(),
|
||||||
|
serde_json::to_string(&FileActionLog {
|
||||||
|
id: d.id,
|
||||||
|
conn_id: self.conn_id,
|
||||||
|
path: d.path,
|
||||||
|
dir: true,
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)));
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub struct PortableState {
|
pub struct PortableState {
|
||||||
pub last_uac: bool,
|
pub last_uac: bool,
|
||||||
|
@ -63,7 +63,7 @@ impl InvokeUiCM for SciterHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_transfer_log(&self, _log: String) {}
|
fn file_transfer_log(&self, _action: &str, _log: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SciterHandler {
|
impl SciterHandler {
|
||||||
|
@ -101,7 +101,7 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized {
|
|||||||
|
|
||||||
fn update_voice_call_state(&self, client: &Client);
|
fn update_voice_call_state(&self, client: &Client);
|
||||||
|
|
||||||
fn file_transfer_log(&self, log: String);
|
fn file_transfer_log(&self, action: &str, log: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InvokeUiCM> Deref for ConnectionManager<T> {
|
impl<T: InvokeUiCM> Deref for ConnectionManager<T> {
|
||||||
@ -418,10 +418,10 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
|||||||
handle_fs(fs, &mut write_jobs, &self.tx, Some(&tx_log)).await;
|
handle_fs(fs, &mut write_jobs, &self.tx, Some(&tx_log)).await;
|
||||||
}
|
}
|
||||||
let log = fs::serialize_transfer_jobs(&write_jobs);
|
let log = fs::serialize_transfer_jobs(&write_jobs);
|
||||||
self.cm.ui_handler.file_transfer_log(log);
|
self.cm.ui_handler.file_transfer_log("transfer", &log);
|
||||||
}
|
}
|
||||||
Data::FileTransferLog(log) => {
|
Data::FileTransferLog((action, log)) => {
|
||||||
self.cm.ui_handler.file_transfer_log(log);
|
self.cm.ui_handler.file_transfer_log(&action, &log);
|
||||||
}
|
}
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
Data::ClipboardFile(_clip) => {
|
Data::ClipboardFile(_clip) => {
|
||||||
@ -526,7 +526,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(job_log) = rx_log.recv() => {
|
Some(job_log) = rx_log.recv() => {
|
||||||
self.cm.ui_handler.file_transfer_log(job_log);
|
self.cm.ui_handler.file_transfer_log("transfer", &job_log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user