Copy to external drives when drag and dropping, part of #828
This commit is contained in:
parent
dd98622cfa
commit
5573e36400
5 changed files with 82 additions and 34 deletions
16
src/app.rs
16
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::<Location>(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::<Tab>(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 {
|
||||
|
|
|
|||
|
|
@ -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<u8>, 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 {
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
to: PathBuf,
|
||||
moving: bool,
|
||||
method: Method,
|
||||
msg_tx: &Arc<TokioMutex<Sender<Message>>>,
|
||||
controller: Controller,
|
||||
) -> Result<OperationSelection, OperationError> {
|
||||
|
|
@ -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<PathBuf>,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<u8>,
|
||||
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<bool, String> {
|
||||
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<bool>,
|
||||
/// Cleanup operation should be skipped
|
||||
pub cleanup: Cell<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Op {
|
||||
pub kind: OpKind,
|
||||
pub from: PathBuf,
|
||||
pub to: PathBuf,
|
||||
pub skipped: Rc<Cell<bool>>,
|
||||
pub skipped: Rc<Skip>,
|
||||
pub is_cleanup: bool,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn move_cleanup_op(&self) -> Option<Self> {
|
||||
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<bool, Box<dyn Error>> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue