parent
db4802056a
commit
14d485a7cb
1 changed files with 117 additions and 9 deletions
126
src/operation.rs
126
src/operation.rs
|
|
@ -1,5 +1,12 @@
|
||||||
use cosmic::iced::futures::{channel::mpsc, executor, SinkExt};
|
use cosmic::iced::futures::{channel::mpsc, executor, SinkExt};
|
||||||
use std::{fs, path::PathBuf, sync::Arc};
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{
|
||||||
|
atomic::{self, AtomicU64},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
||||||
|
|
@ -58,26 +65,127 @@ impl Operation {
|
||||||
//TODO: SAFELY HANDLE CANCEL
|
//TODO: SAFELY HANDLE CANCEL
|
||||||
match self {
|
match self {
|
||||||
Self::Copy { paths, to } => {
|
Self::Copy { paths, to } => {
|
||||||
|
// Handle duplicate file names by renaming paths
|
||||||
|
let (paths, to): (Vec<_>, Vec<_>) = tokio::task::spawn_blocking(move || {
|
||||||
|
paths
|
||||||
|
.into_iter()
|
||||||
|
.zip(std::iter::repeat(to.as_path()))
|
||||||
|
.map(|(from, to)| {
|
||||||
|
log::info!("{:?}", from.parent());
|
||||||
|
if matches!(from.parent(), Some(parent) if parent == to) {
|
||||||
|
// `from`'s parent is equal to `to` which means we're copying to the same
|
||||||
|
// directory (duplicating files)
|
||||||
|
let mut to = to.to_owned();
|
||||||
|
let to = if let Some(full_name) =
|
||||||
|
from.file_name().and_then(|name| name.to_str())
|
||||||
|
{
|
||||||
|
// Separate the full file name into its file name plus extension.
|
||||||
|
let (base_name, ext, needs_dot) = if full_name.starts_with('.')
|
||||||
|
{
|
||||||
|
// `[Path::file_name]` returns the full name for dotfiles (e.g.
|
||||||
|
// .someconf is the file_name)
|
||||||
|
(full_name, "", false)
|
||||||
|
} else {
|
||||||
|
// Consider everything beyond the first '.' to be a file
|
||||||
|
// extension.
|
||||||
|
full_name
|
||||||
|
.split_once('.')
|
||||||
|
.map(|(full_name, extension)| {
|
||||||
|
(full_name, extension, !extension.is_empty())
|
||||||
|
})
|
||||||
|
// File without an extension
|
||||||
|
.unwrap_or((full_name, "", false))
|
||||||
|
};
|
||||||
|
let mut n = 0u32;
|
||||||
|
// Loop until a valid `copy n` variant is found
|
||||||
|
loop {
|
||||||
|
n = if let Some(n) = n.checked_add(1) {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
// TODO: Return error? fs_extra will handle it anyway
|
||||||
|
break to;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rebuild file name
|
||||||
|
let dot = if needs_dot { "." } else { "" };
|
||||||
|
let new_name = format!("{base_name} (Copy {n}){dot}{ext}");
|
||||||
|
to = to.join(new_name);
|
||||||
|
|
||||||
|
if !matches!(to.try_exists(), Ok(true)) {
|
||||||
|
break to;
|
||||||
|
}
|
||||||
|
// Continue if a copy with index exists
|
||||||
|
to.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
to
|
||||||
|
};
|
||||||
|
|
||||||
|
(from, to)
|
||||||
|
} else {
|
||||||
|
(from, to.to_owned())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unzip()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || -> fs_extra::error::Result<()> {
|
||||||
log::info!("Copy {:?} to {:?}", paths, to);
|
log::info!("Copy {:?} to {:?}", paths, to);
|
||||||
let options = fs_extra::dir::CopyOptions::default();
|
let dir_options = fs_extra::dir::CopyOptions::default().copy_inside(true);
|
||||||
//TODO: set options as desired
|
let file_options = fs_extra::file::CopyOptions::default();
|
||||||
fs_extra::copy_items_with_progress(&paths, &to, &options, |progress| {
|
let copied_bytes = AtomicU64::default();
|
||||||
|
let total_bytes = paths
|
||||||
|
.iter()
|
||||||
|
.map(fs_extra::dir::get_size)
|
||||||
|
.sum::<Result<u64, _>>()?;
|
||||||
|
let handler = || {
|
||||||
executor::block_on(async {
|
executor::block_on(async {
|
||||||
let _ = msg_tx
|
let _ = msg_tx
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.send(Message::PendingProgress(
|
.send(Message::PendingProgress(
|
||||||
id,
|
id,
|
||||||
100.0 * (progress.copied_bytes as f32)
|
100.0 * copied_bytes.load(atomic::Ordering::Relaxed) as f32
|
||||||
/ (progress.total_bytes as f32),
|
/ total_bytes as f32,
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
});
|
})
|
||||||
|
};
|
||||||
|
let file_handler = |progress: fs_extra::file::TransitProcess| {
|
||||||
|
copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed);
|
||||||
|
handler();
|
||||||
|
};
|
||||||
|
let dir_handler = |progress: fs_extra::TransitProcess| {
|
||||||
|
copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed);
|
||||||
|
handler();
|
||||||
//TODO: handle exceptions
|
//TODO: handle exceptions
|
||||||
fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||||||
})
|
};
|
||||||
|
//TODO: set options as desired
|
||||||
|
for (from, to) in paths.into_iter().zip(to.into_iter()) {
|
||||||
|
// This is essentially what `[fs_extra::copy_items_with_progress]` does
|
||||||
|
// except without handling options (e.g. overwrite). We're currently using
|
||||||
|
// the defaults anyway.
|
||||||
|
if from.is_dir() {
|
||||||
|
fs_extra::copy_items_with_progress(
|
||||||
|
&[from],
|
||||||
|
to,
|
||||||
|
&dir_options,
|
||||||
|
dir_handler,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
fs_extra::file::copy_with_progress(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
&file_options,
|
||||||
|
file_handler,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue