diff --git a/src/app.rs b/src/app.rs index 3e79e78..4690e05 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2263,7 +2263,7 @@ impl Application for App { Message::Cut(entity_opt) => { self.set_cut(entity_opt); let paths = self.selected_paths(entity_opt); - let contents = ClipboardCopy::new(ClipboardKind::Cut, &paths); + let contents = ClipboardCopy::new(ClipboardKind::Cut { is_dnd: false }, &paths); return clipboard::write_data(contents); } Message::CloseToast(id) => { @@ -3010,9 +3010,10 @@ impl Application for App { paths: contents.paths, to, }), - ClipboardKind::Cut => self.operation(Operation::Move { + ClipboardKind::Cut { is_dnd } => self.operation(Operation::Move { paths: contents.paths, to, + cross_device_copy: is_dnd, }), }; } @@ -3060,7 +3061,10 @@ impl Application for App { if self.update_favorites(&[(from.clone(), to.clone())]) { commands.push(self.update_config()); } - } else if let Operation::Move { ref paths, ref to } = op { + } else if let Operation::Move { + ref paths, ref to, .. + } = op + { let path_changes: Vec<_> = paths .iter() .filter_map(|from| { @@ -3527,7 +3531,7 @@ impl Application for App { cosmic::action::app(Message::CutPaths(match p { Some(s) => match s.kind { ClipboardKind::Copy => Vec::new(), - ClipboardKind::Cut => s.paths, + ClipboardKind::Cut { .. } => s.paths, }, None => Vec::new(), })) @@ -3712,7 +3716,7 @@ impl Application for App { self.nav_dnd_hover = None; if let Some((location, data)) = self.nav_model.data::(entity).zip(data) { let kind = match action { - DndAction::Move => ClipboardKind::Cut, + DndAction::Move => ClipboardKind::Cut { is_dnd: true }, _ => ClipboardKind::Copy, }; let ret = match location { @@ -3772,7 +3776,7 @@ impl Application for App { self.nav_dnd_hover = None; if let Some((tab, data)) = self.tab_model.data::(entity).zip(data) { let kind = match action { - DndAction::Move => ClipboardKind::Cut, + DndAction::Move => ClipboardKind::Cut { is_dnd: true }, _ => ClipboardKind::Copy, }; let ret = match &tab.location { diff --git a/src/clipboard.rs b/src/clipboard.rs index 9fda588..a61dcb3 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -13,7 +13,7 @@ use url::Url; #[derive(Clone, Copy, Debug)] pub enum ClipboardKind { Copy, - Cut, + Cut { is_dnd: bool }, } #[derive(Clone, Debug)] @@ -37,7 +37,7 @@ impl ClipboardCopy { let mut text_uri_list = String::new(); let mut x_special_gnome_copied_files = match kind { ClipboardKind::Copy => "copy", - ClipboardKind::Cut => "cut", + ClipboardKind::Cut { .. } => "cut", } .to_string(); //TODO: do we have to use \r\n? @@ -145,7 +145,7 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { if i == 0 { kind = match line { "copy" => ClipboardKind::Copy, - "cut" => ClipboardKind::Cut, + "cut" => ClipboardKind::Cut { is_dnd: false }, _ => Err(format!("unsupported clipboard operation {:?}", line))?, }; } else { diff --git a/src/operation/mod.rs b/src/operation/mod.rs index c8e4197..b91ae3d 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -27,7 +27,7 @@ pub mod controller; use self::reader::OpReader; pub mod reader; -use self::recursive::Context; +use self::recursive::{Context, Method}; pub mod recursive; async fn handle_replace( @@ -267,7 +267,7 @@ pub enum ReplaceResult { async fn copy_or_move( paths: Vec, to: PathBuf, - moving: bool, + method: Method, msg_tx: &Arc>>, controller: Controller, ) -> Result { @@ -276,7 +276,10 @@ async fn copy_or_move( compio::runtime::spawn(async move { log::info!( "{} {:?} to {:?}", - if moving { "Move" } else { "Copy" }, + match method { + Method::Copy => "Copy", + Method::Move { .. } => "Move", + }, paths, to ); @@ -286,7 +289,9 @@ async fn copy_or_move( .into_iter() .zip(std::iter::repeat(to.as_path())) .filter_map(|(from, to)| { - if matches!(from.parent(), Some(parent) if parent == to) && !moving { + if matches!(from.parent(), Some(parent) if parent == to) + && matches!(method, Method::Copy) + { // `from`'s parent is equal to `to` which means we're copying to the same // directory (duplicating files) let to = copy_unique_path(&from, to); @@ -330,7 +335,7 @@ async fn copy_or_move( } context - .recursive_copy_or_move(from_to_pairs, moving) + .recursive_copy_or_move(from_to_pairs, method) .await .map_err(OperationError::from_str)?; @@ -483,6 +488,7 @@ pub enum Operation { Move { paths: Vec, to: PathBuf, + cross_device_copy: bool, }, NewFile { path: PathBuf, @@ -576,7 +582,7 @@ impl Operation { to = file_name(to), progress = progress() ), - Self::Move { paths, to } => fl!( + Self::Move { paths, to, .. } => fl!( "moving", items = paths.len(), from = paths_parent_name(paths), @@ -635,7 +641,7 @@ impl Operation { from = paths_parent_name(paths), to = file_name(to) ), - Self::Move { paths, to } => fl!( + Self::Move { paths, to, .. } => fl!( "moved", items = paths.len(), from = paths_parent_name(paths), @@ -853,7 +859,9 @@ impl Operation { .map_err(wrap_compio_spawn_error)? .map_err(OperationError::from_str) } - Self::Copy { paths, to } => copy_or_move(paths, to, false, msg_tx, controller).await, + Self::Copy { paths, to } => { + copy_or_move(paths, to, Method::Copy, msg_tx, controller).await + } Self::Delete { paths } => { let total = paths.len(); for (i, path) in paths.into_iter().enumerate() { @@ -1027,7 +1035,20 @@ impl Operation { .await .map_err(wrap_compio_spawn_error)? .map_err(OperationError::from_str), - Self::Move { paths, to } => copy_or_move(paths, to, true, msg_tx, controller).await, + Self::Move { + paths, + to, + cross_device_copy, + } => { + copy_or_move( + paths, + to, + Method::Move { cross_device_copy }, + msg_tx, + controller, + ) + .await + } Self::NewFolder { path } => compio::runtime::spawn(async move { controller.check().await.map_err(OperationError::from_str)?; compio::fs::create_dir(&path) diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index ba2d594..dc49f44 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -9,6 +9,11 @@ use walkdir::WalkDir; use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult}; +pub enum Method { + Copy, + Move { cross_device_copy: bool }, +} + pub struct Context { buf: Vec, controller: Controller, @@ -46,7 +51,7 @@ impl Context { pub async fn recursive_copy_or_move( &mut self, from_to_pairs: Vec<(PathBuf, PathBuf)>, - moving: bool, + method: Method, ) -> Result { let mut ops = Vec::new(); let mut cleanup_ops = Vec::new(); @@ -69,10 +74,9 @@ impl Context { let kind = if file_type.is_dir() { OpKind::Mkdir } else if file_type.is_file() { - if moving { - OpKind::Move - } else { - OpKind::Copy + match method { + Method::Copy => OpKind::Copy, + Method::Move { cross_device_copy } => OpKind::Move { cross_device_copy }, } } else if file_type.is_symlink() { let target = fs::read_link(&from) @@ -99,9 +103,13 @@ impl Context { kind, from, to, - skipped: Rc::new(Cell::new(false)), + skipped: Rc::new(Skip { + normal: Cell::new(false), + cleanup: Cell::new(false), + }), + is_cleanup: false, }; - if moving { + if matches!(method, Method::Move { .. }) { if let Some(cleanup_op) = op.move_cleanup_op() { cleanup_ops.push(cleanup_op); } @@ -180,7 +188,7 @@ impl Context { if apply_to_all { self.replace_result_opt = Some(replace_result); } - op.skipped.set(true); + op.skipped.normal.set(true); Ok(ControlFlow::Break(true)) } ReplaceResult::Cancel => Ok(ControlFlow::Break(false)), @@ -199,25 +207,34 @@ pub struct Progress { #[derive(Debug)] pub enum OpKind { Copy, - Move, + Move { cross_device_copy: bool }, Mkdir, Remove, Rmdir, Symlink { target: PathBuf }, } +#[derive(Debug)] +pub struct Skip { + /// Normal operation should be skipped + pub normal: Cell, + /// Cleanup operation should be skipped + pub cleanup: Cell, +} + #[derive(Debug)] pub struct Op { pub kind: OpKind, pub from: PathBuf, pub to: PathBuf, - pub skipped: Rc>, + pub skipped: Rc, + pub is_cleanup: bool, } impl Op { fn move_cleanup_op(&self) -> Option { let kind = match self.kind { - OpKind::Copy | OpKind::Move | OpKind::Symlink { .. } => OpKind::Remove, + OpKind::Copy | OpKind::Move { .. } | OpKind::Symlink { .. } => OpKind::Remove, OpKind::Mkdir => OpKind::Rmdir, OpKind::Remove | OpKind::Rmdir => return None, }; @@ -227,6 +244,7 @@ impl Op { //TODO: it is strange to have `to` here to: self.to.clone(), skipped: self.skipped.clone(), + is_cleanup: true, }) } @@ -235,7 +253,7 @@ impl Op { ctx: &mut Context, mut progress: Progress, ) -> Result> { - if self.skipped.get() { + if self.skipped.normal.get() || (self.is_cleanup && self.skipped.cleanup.get()) { return Ok(true); } match self.kind { @@ -326,7 +344,7 @@ impl Op { to_file.sync_all().await?; } - OpKind::Move => { + OpKind::Move { cross_device_copy } => { // Remove `to` if overwriting and it is an existing file if self.to.is_file() { match ctx.replace(self).await? { @@ -349,12 +367,17 @@ impl Op { const EXDEV: i32 = libc::EXDEV as _; if err.raw_os_error() == Some(EXDEV) { + if cross_device_copy { + // Do not clean up if cross_device_copy is set + self.skipped.cleanup.set(true); + } // Try standard copy if hard link fails with cross device error let mut copy_op = Op { kind: OpKind::Copy, from: self.from.clone(), to: self.to.clone(), skipped: self.skipped.clone(), + is_cleanup: self.is_cleanup, }; return Box::pin(copy_op.run(ctx, progress)).await; } else { diff --git a/src/tab.rs b/src/tab.rs index 3f2e31c..03224b3 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -3425,7 +3425,7 @@ impl Tab { } commands.push(Command::DropFiles(to, from)) } - Location::Trash if matches!(from.kind, ClipboardKind::Cut) => { + Location::Trash if matches!(from.kind, ClipboardKind::Cut { .. }) => { commands.push(Command::Delete(from.paths)) } _ => { @@ -3676,7 +3676,7 @@ impl Tab { if action == DndAction::Copy { Message::Drop(Some((location1.clone(), data))) } else if action == DndAction::Move { - data.kind = ClipboardKind::Cut; + data.kind = ClipboardKind::Cut { is_dnd: true }; Message::Drop(Some((location1.clone(), data))) } else { log::warn!("unsupported action: {:?}", action); @@ -5009,7 +5009,7 @@ impl Tab { if action == DndAction::Copy { Message::Drop(Some((tab_location.clone(), data))) } else if action == DndAction::Move { - data.kind = ClipboardKind::Cut; + data.kind = ClipboardKind::Cut { is_dnd: true }; Message::Drop(Some((tab_location.clone(), data))) } else { log::warn!("unsupported action: {:?}", action);