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