file rename (#9089)

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages 2024-08-16 12:55:58 +08:00 committed by GitHub
parent 579e0fac36
commit ed18e3c786
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 507 additions and 50 deletions

View File

@ -262,6 +262,7 @@ class _FileManagerPageState extends State<FileManagerPage>
Offstage(
offstage: item.state != JobState.paused,
child: MenuButton(
tooltip: translate("Resume"),
onPressed: () {
jobController.resumeJob(item.id);
},
@ -274,6 +275,7 @@ class _FileManagerPageState extends State<FileManagerPage>
),
),
MenuButton(
tooltip: translate("Delete"),
padding: EdgeInsets.only(right: 15),
child: SvgPicture.asset(
"assets/close.svg",
@ -521,6 +523,7 @@ class _FileManagerViewState extends State<FileManagerView> {
Row(
children: [
MenuButton(
tooltip: translate('Back'),
padding: EdgeInsets.only(
right: 3,
),
@ -540,6 +543,7 @@ class _FileManagerViewState extends State<FileManagerView> {
},
),
MenuButton(
tooltip: translate('Parent directory'),
child: RotatedBox(
quarterTurns: 3,
child: SvgPicture.asset(
@ -604,6 +608,7 @@ class _FileManagerViewState extends State<FileManagerView> {
switch (_locationStatus.value) {
case LocationStatus.bread:
return MenuButton(
tooltip: translate('Search'),
onPressed: () {
_locationStatus.value = LocationStatus.fileSearchBar;
Future.delayed(
@ -630,6 +635,7 @@ class _FileManagerViewState extends State<FileManagerView> {
);
case LocationStatus.fileSearchBar:
return MenuButton(
tooltip: translate('Clear'),
onPressed: () {
onSearchText("", isLocal);
_locationStatus.value = LocationStatus.bread;
@ -645,6 +651,7 @@ class _FileManagerViewState extends State<FileManagerView> {
}
}),
MenuButton(
tooltip: translate('Refresh File'),
padding: EdgeInsets.only(
left: 3,
),
@ -670,6 +677,7 @@ class _FileManagerViewState extends State<FileManagerView> {
isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
children: [
MenuButton(
tooltip: translate('Home'),
padding: EdgeInsets.only(
right: 3,
),
@ -685,11 +693,27 @@ class _FileManagerViewState extends State<FileManagerView> {
hoverColor: Theme.of(context).hoverColor,
),
MenuButton(
tooltip: translate('Create Folder'),
onPressed: () {
final name = TextEditingController();
String? errorText;
_ffi.dialogManager.show((setState, close, context) {
name.addListener(() {
if (errorText != null) {
setState(() {
errorText = null;
});
}
});
submit() {
if (name.value.text.isNotEmpty) {
if (!PathUtil.validName(name.value.text,
controller.options.value.isWindows)) {
setState(() {
errorText = translate("Invalid folder name");
});
return;
}
controller.createDir(PathUtil.join(
controller.directory.value.path,
name.value.text,
@ -721,6 +745,7 @@ class _FileManagerViewState extends State<FileManagerView> {
labelText: translate(
"Please enter the folder name",
),
errorText: errorText,
),
controller: name,
autofocus: true,
@ -754,6 +779,7 @@ class _FileManagerViewState extends State<FileManagerView> {
hoverColor: Theme.of(context).hoverColor,
),
Obx(() => MenuButton(
tooltip: translate('Delete'),
onPressed: SelectedItems.valid(selectedItems.items)
? () async {
await (controller
@ -885,6 +911,7 @@ class _FileManagerViewState extends State<FileManagerView> {
menuPos = RelativeRect.fromLTRB(x, y, x, y);
},
child: MenuButton(
tooltip: translate('More'),
onPressed: () => mod_menu.showMenu(
context: context,
position: menuPos,
@ -974,6 +1001,7 @@ class _FileManagerViewState extends State<FileManagerView> {
final lastModifiedStr = entry.isDrive
? " "
: "${entry.lastModified().toString().replaceAll(".000", "")} ";
var secondaryPosition = RelativeRect.fromLTRB(0, 0, 0, 0);
return Padding(
padding: EdgeInsets.symmetric(vertical: 1),
child: Obx(() => Container(
@ -1038,6 +1066,35 @@ class _FileManagerViewState extends State<FileManagerView> {
_onSelectedChanged(
items, filteredEntries, entry, isLocal);
},
onSecondaryTap: () {
final items = [
if (!entry.isDrive &&
versionCmp(_ffi.ffiModel.pi.version,
"1.3.0") >=
0)
mod_menu.PopupMenuItem(
child: Text("Rename"),
height: CustomPopupMenuTheme.height,
onTap: () {
controller.renameAction(entry, isLocal);
},
)
];
if (items.isNotEmpty) {
mod_menu.showMenu(
context: context,
position: secondaryPosition,
items: items,
);
}
},
onSecondaryTapDown: (details) {
secondaryPosition = RelativeRect.fromLTRB(
details.globalPosition.dx,
details.globalPosition.dy,
details.globalPosition.dx,
details.globalPosition.dy);
},
),
SizedBox(
width: 2.0,

View File

@ -1157,6 +1157,16 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
Text(translate('Create Folder'))
],
);
case CmFileAction.rename:
return Column(
children: [
Icon(
Icons.drive_file_move_outlined,
color: Theme.of(context).tabBarTheme.labelColor,
),
Text(translate('Rename'))
],
);
}
}

View File

@ -34,6 +34,7 @@ class _MenuButtonState extends State<MenuButton> {
return Padding(
padding: widget.padding,
child: Tooltip(
waitDuration: Duration(milliseconds: 300),
message: widget.tooltip,
child: Material(
type: MaterialType.transparency,

View File

@ -204,36 +204,54 @@ class _FileManagerPageState extends State<FileManagerPage> {
setState(() {});
} else if (v == "folder") {
final name = TextEditingController();
gFFI.dialogManager
.show((setState, close, context) => CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: translate(
"Please enter the folder name"),
),
controller: name,
),
],
String? errorText;
gFFI.dialogManager.show((setState, close, context) {
name.addListener(() {
if (errorText != null) {
setState(() {
errorText = null;
});
}
});
return CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText:
translate("Please enter the folder name"),
errorText: errorText,
),
actions: [
dialogButton("Cancel",
onPressed: () => close(false),
isOutline: true),
dialogButton("OK", onPressed: () {
if (name.value.text.isNotEmpty) {
currentFileController.createDir(
PathUtil.join(
currentDir.path,
name.value.text,
currentOptions.isWindows));
close();
}
})
]));
controller: name,
),
],
),
actions: [
dialogButton("Cancel",
onPressed: () => close(false), isOutline: true),
dialogButton("OK", onPressed: () {
if (name.value.text.isNotEmpty) {
if (!PathUtil.validName(
name.value.text,
currentFileController
.options.value.isWindows)) {
setState(() {
errorText =
translate("Invalid folder name");
});
return;
}
currentFileController.createDir(PathUtil.join(
currentDir.path,
name.value.text,
currentOptions.isWindows));
close();
}
})
]);
});
} else if (v == "hidden") {
currentFileController.toggleShowHidden();
}
@ -497,7 +515,15 @@ class _FileManagerViewState extends State<FileManagerView> {
child: Text(translate("Properties")),
value: "properties",
enabled: false,
)
),
if (!entries[index].isDrive &&
versionCmp(gFFI.ffiModel.pi.version,
"1.3.0") >=
0)
PopupMenuItem(
child: Text(translate("Rename")),
value: "rename",
)
];
},
onSelected: (v) {
@ -509,6 +535,9 @@ class _FileManagerViewState extends State<FileManagerView> {
_selectedItems.clear();
widget.selectMode.toggle(isLocal);
setState(() {});
} else if (v == "rename") {
controller.renameAction(
entries[index], isLocal);
}
}),
onTap: () {

View File

@ -291,7 +291,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate('Enable trusted devices')),
Text(translate('enable-trusted-devices-tip'),
Text('* ${translate('enable-trusted-devices-tip')}',
style: Theme.of(context).textTheme.bodySmall),
],
),

View File

@ -33,6 +33,8 @@ class CmFileModel {
_onFileRemove(evt['remove']);
} else if (evt['create_dir'] != null) {
_onDirCreate(evt['create_dir']);
} else if (evt['rename'] != null) {
_onRename(evt['rename']);
}
}
@ -59,8 +61,6 @@ class CmFileModel {
_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");
@ -70,12 +70,7 @@ class CmFileModel {
if (job == null) {
job = CmFileLog();
jobTable.add(job);
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
if (!(gFFI.chatModel.isShowCMSidePage &&
currentSelectedTab.key == data.connId.toString())) {
client?.unreadChatMessageCount.value += 1;
}
_addUnread(data.connId);
}
job.id = data.id;
job.action =
@ -167,8 +162,6 @@ class CmFileModel {
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");
@ -179,17 +172,45 @@ class CmFileModel {
..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;
}
_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 {
@ -198,6 +219,7 @@ enum CmFileAction {
localToRemote,
remove,
createDir,
rename,
}
class CmFileLog {
@ -285,3 +307,22 @@ class FileActionLog {
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'] ?? '',
);
}

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart';
import 'package:flutter_hbb/utils/event_loop.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as path;
@ -642,6 +643,77 @@ class FileController {
path: path,
isRemote: !isLocal);
}
Future<void> renameAction(Entry item, bool isLocal) async {
final textEditingController = TextEditingController(text: item.name);
String? errorText;
dialogManager?.show((setState, close, context) {
textEditingController.addListener(() {
if (errorText != null) {
setState(() {
errorText = null;
});
}
});
submit() async {
final newName = textEditingController.text;
if (newName.isEmpty || newName == item.name) {
close();
return;
}
if (directory.value.entries.any((e) => e.name == newName)) {
setState(() {
errorText = translate("Already exists");
});
return;
}
if (!PathUtil.validName(newName, options.value.isWindows)) {
setState(() {
if (item.isDirectory) {
errorText = translate("Invalid folder name");
} else {
errorText = translate("Invalid file name");
}
});
return;
}
await bind.sessionRenameFile(
sessionId: sessionId,
actId: JobController.jobID.next(),
path: item.path,
newName: newName,
isRemote: !isLocal);
close();
}
return CustomAlertDialog(
content: Column(
children: [
DialogTextField(
title: '${translate('Rename')} ${item.name}',
controller: textEditingController,
errorText: errorText,
),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
}
}
class JobController {
@ -1083,6 +1155,13 @@ class PathUtil {
final pathUtil = isWindows ? windowsContext : posixContext;
return pathUtil.dirname(path);
}
static bool validName(String name, bool isWindows) {
final unixFileNamePattern = RegExp(r'^[^/\0]+$');
final windowsFileNamePattern = RegExp(r'^[^<>:"/\\|?*]+$');
final reg = isWindows ? windowsFileNamePattern : unixFileNamePattern;
return reg.hasMatch(name);
}
}
class DirectoryOptions {

View File

@ -371,6 +371,12 @@ message ReadAllFiles {
bool include_hidden = 3;
}
message FileRename {
int32 id = 1;
string path = 2;
string new_name = 3;
}
message FileAction {
oneof union {
ReadDir read_dir = 1;
@ -382,6 +388,7 @@ message FileAction {
ReadAllFiles all_files = 7;
FileTransferCancel cancel = 8;
FileTransferSendConfirmRequest send_confirm = 9;
FileRename rename = 10;
}
}

View File

@ -838,6 +838,21 @@ pub fn create_dir(dir: &str) -> ResultType<()> {
Ok(())
}
#[inline]
pub fn rename_file(path: &str, new_name: &str) -> ResultType<()> {
let path = std::path::Path::new(&path);
if path.exists() {
let dir = path
.parent()
.ok_or(anyhow!("Parent directoy of {path:?} not exists"))?;
let new_path = dir.join(&new_name);
std::fs::rename(&path, &new_path)?;
Ok(())
} else {
bail!("{path:?} not exists");
}
}
#[inline]
pub fn transform_windows_path(entries: &mut Vec<FileEntry>) {
for entry in entries {

View File

@ -3180,6 +3180,7 @@ pub enum Data {
NewVoiceCall,
CloseVoiceCall,
ResetDecoder(Option<usize>),
RenameFile((i32, String, String, bool)),
}
/// Keycode for key events.

View File

@ -1,4 +1,4 @@
use hbb_common::{fs, message_proto::*, log};
use hbb_common::{fs, log, message_proto::*};
use super::{Data, Interface};
@ -7,7 +7,12 @@ pub trait FileManager: Interface {
fs::get_home_as_string()
}
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli", feature = "flutter")))]
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn read_dir(&self, path: String, include_hidden: bool) -> sciter::Value {
match fs::read_dir(&fs::get_path(&path), include_hidden) {
Err(_) => sciter::Value::null(),
@ -20,7 +25,12 @@ pub trait FileManager: Interface {
}
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli", feature = "flutter"))]
#[cfg(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
))]
fn read_dir(&self, path: &str, include_hidden: bool) -> String {
use crate::common::make_fd_to_json;
match fs::read_dir(&fs::get_path(path), include_hidden) {
@ -136,4 +146,8 @@ pub trait FileManager: Interface {
is_upload,
)));
}
fn rename_file(&self, act_id: i32, path: String, new_name: String, is_remote: bool) {
self.send(Data::RenameFile((act_id, path, new_name, is_remote)));
}
}

View File

@ -817,6 +817,25 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
Data::RenameFile((id, path, new_name, is_remote)) => {
if is_remote {
let mut msg_out = Message::new();
let mut file_action = FileAction::new();
file_action.set_rename(FileRename {
id,
path,
new_name,
..Default::default()
});
msg_out.set_file_action(file_action);
allow_err!(peer.send(&msg_out).await);
} else {
let err = fs::rename_file(&path, &new_name)
.err()
.map(|e| e.to_string());
self.handle_job_status(id, -1, err);
}
}
Data::RecordScreen(start, display, w, h, id) => {
let _ = self
.video_sender

View File

@ -710,6 +710,18 @@ pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
}
}
pub fn session_rename_file(
session_id: SessionID,
act_id: i32,
path: String,
new_name: String,
is_remote: bool,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.rename_file(act_id, path, new_name, is_remote);
}
}
pub fn session_elevate_direct(session_id: SessionID) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.elevate_direct();

View File

@ -102,6 +102,11 @@ pub enum FS {
last_modified: u64,
is_upload: bool,
},
Rename {
id: i32,
path: String,
new_name: String,
},
}
#[cfg(target_os = "windows")]

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "平台"),
("Days remaining", "剩余天数"),
("enable-trusted-devices-tip", "允许受信任的设备跳过 2FA 验证"),
("Parent directory", "父目录"),
("Resume", "继续"),
("Invalid file name", "无效文件名"),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "Platforma"),
("Days remaining", "Zbývajících dnů"),
("enable-trusted-devices-tip", "Přeskočte 2FA ověření na důvěryhodných zařízeních"),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "Plattform"),
("Days remaining", "Verbleibende Tage"),
("enable-trusted-devices-tip", "2FA-Verifizierung auf vertrauenswürdigen Geräten überspringen"),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "Plataforma"),
("Days remaining", "Días restantes"),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "Piattaforma"),
("Days remaining", "Giorni rimanenti"),
("enable-trusted-devices-tip", "Salta verifica 2FA nei dispositivi attendibili"),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", "플랫폼"),
("Days remaining", "일 남음"),
("enable-trusted-devices-tip", "신뢰할 수 있는 기기에서 2FA 검증 건너뛰기"),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -641,5 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Platform", ""),
("Days remaining", ""),
("enable-trusted-devices-tip", ""),
("Parent directory", ""),
("Resume", ""),
("Invalid file name", ""),
].iter().cloned().collect();
}

View File

@ -2274,6 +2274,22 @@ impl Connection {
job.confirm(&r);
}
}
Some(file_action::Union::Rename(r)) => {
self.send_fs(ipc::FS::Rename {
id: r.id,
path: r.path.clone(),
new_name: r.new_name.clone(),
});
self.send_to_cm(ipc::Data::FileTransferLog((
"rename".to_string(),
serde_json::to_string(&FileRenameLog {
conn_id: self.inner.id(),
path: r.path,
new_name: r.new_name,
})
.unwrap_or_default(),
)));
}
_ => {}
}
}
@ -3451,6 +3467,14 @@ struct FileActionLog {
dir: bool,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileRenameLog {
conn_id: i32,
path: String,
new_name: String,
}
struct FileRemoveLogControl {
conn_id: i32,
instant: Instant,

View File

@ -881,6 +881,9 @@ async fn handle_fs(
}
}
}
ipc::FS::Rename { id, path, new_name } => {
rename_file(path, new_name, id, tx).await;
}
_ => {}
}
}
@ -945,6 +948,17 @@ async fn create_dir(path: String, id: i32, tx: &UnboundedSender<Data>) {
.await;
}
#[cfg(not(any(target_os = "ios")))]
async fn rename_file(path: String, new_name: String, id: i32, tx: &UnboundedSender<Data>) {
handle_result(
spawn_blocking(move || fs::rename_file(&path, &new_name)).await,
id,
0,
tx,
)
.await;
}
#[cfg(not(any(target_os = "ios")))]
async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender<Data>) {
let path = fs::get_path(&path);