Add dialog for move conflict, part of #180
This commit is contained in:
parent
f075d07bfd
commit
35fcb011c5
6 changed files with 305 additions and 147 deletions
40
src/app.rs
40
src/app.rs
|
|
@ -40,6 +40,7 @@ use std::{
|
|||
sync::Arc,
|
||||
time::{self, Instant},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::localize::LANGUAGE_SORTER;
|
||||
use crate::tab::HOVER_DURATION;
|
||||
|
|
@ -208,6 +209,7 @@ pub enum Message {
|
|||
Cut(Option<Entity>),
|
||||
DialogCancel,
|
||||
DialogComplete,
|
||||
DialogPush(DialogPage),
|
||||
DialogUpdate(DialogPage),
|
||||
EditLocation(Option<Entity>),
|
||||
Key(Modifiers, Key),
|
||||
|
|
@ -280,7 +282,7 @@ impl ContextPage {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DialogPage {
|
||||
EmptyTrash,
|
||||
FailedOperation(u64),
|
||||
|
|
@ -295,6 +297,11 @@ pub enum DialogPage {
|
|||
name: String,
|
||||
dir: bool,
|
||||
},
|
||||
Replace {
|
||||
from: tab::Item,
|
||||
to: tab::Item,
|
||||
tx: mpsc::Sender<fs_extra::dir::TransitProcessResult>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct FavoriteIndex(usize);
|
||||
|
|
@ -1197,12 +1204,27 @@ impl Application for App {
|
|||
let to = parent.join(name);
|
||||
self.operation(Operation::Rename { from, to });
|
||||
}
|
||||
DialogPage::Replace { tx, .. } => {
|
||||
return Command::perform(
|
||||
async move {
|
||||
let _ = tx
|
||||
.send(fs_extra::dir::TransitProcessResult::Overwrite)
|
||||
.await;
|
||||
message::none()
|
||||
},
|
||||
|x| x,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::DialogPush(dialog_page) => {
|
||||
self.dialog_pages.push_back(dialog_page);
|
||||
}
|
||||
Message::DialogUpdate(dialog_page) => {
|
||||
//TODO: panicless way to do this?
|
||||
self.dialog_pages[0] = dialog_page;
|
||||
if !self.dialog_pages.is_empty() {
|
||||
self.dialog_pages[0] = dialog_page;
|
||||
}
|
||||
}
|
||||
Message::EditLocation(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
|
|
@ -2226,6 +2248,18 @@ impl Application for App {
|
|||
.spacing(space_xxs),
|
||||
)
|
||||
}
|
||||
DialogPage::Replace { from, to, .. } => {
|
||||
widget::dialog(fl!("replace-title", filename = to.name.as_str()))
|
||||
.body(fl!("replace-warning-operation"))
|
||||
.control(to.replace_view(fl!("original-file"), IconSizes::default()))
|
||||
.control(from.replace_view(fl!("replace-with"), IconSizes::default()))
|
||||
.primary_action(
|
||||
widget::button::suggested(fl!("replace")).on_press(Message::DialogComplete),
|
||||
)
|
||||
.secondary_action(
|
||||
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Some(dialog.into())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use cosmic::iced::futures::{channel::mpsc, executor, SinkExt};
|
||||
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
|
|
@ -7,8 +7,13 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
|
||||
use crate::{app::Message, fl};
|
||||
use crate::{
|
||||
app::{DialogPage, Message},
|
||||
config::IconSizes,
|
||||
fl, tab,
|
||||
};
|
||||
|
||||
fn err_str<T: ToString>(err: T) -> String {
|
||||
err.to_string()
|
||||
|
|
@ -53,7 +58,7 @@ impl Operation {
|
|||
pub async fn perform(
|
||||
self,
|
||||
id: u64,
|
||||
msg_tx: &Arc<tokio::sync::Mutex<mpsc::Sender<Message>>>,
|
||||
msg_tx: &Arc<Mutex<Sender<Message>>>,
|
||||
) -> Result<(), String> {
|
||||
let _ = msg_tx
|
||||
.lock()
|
||||
|
|
@ -247,8 +252,59 @@ impl Operation {
|
|||
))
|
||||
.await;
|
||||
});
|
||||
//TODO: handle exceptions
|
||||
fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||||
|
||||
match progress.state {
|
||||
fs_extra::dir::TransitState::Normal => {
|
||||
fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||||
}
|
||||
fs_extra::dir::TransitState::Exists => {
|
||||
let Some(file_from) = progress.file_from.clone() else {
|
||||
log::warn!("missing file_from in progress");
|
||||
return fs_extra::dir::TransitProcessResult::Abort;
|
||||
};
|
||||
let item_from =
|
||||
match tab::item_from_path(file_from, IconSizes::default()) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("{}", err);
|
||||
return fs_extra::dir::TransitProcessResult::Abort;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(file_to) = progress.file_to.clone() else {
|
||||
log::warn!("missing file_to in progress");
|
||||
return fs_extra::dir::TransitProcessResult::Abort;
|
||||
};
|
||||
let item_to =
|
||||
match tab::item_from_path(file_to, IconSizes::default()) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("{}", err);
|
||||
return fs_extra::dir::TransitProcessResult::Abort;
|
||||
}
|
||||
};
|
||||
|
||||
executor::block_on(async {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let _ = msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::DialogPush(DialogPage::Replace {
|
||||
from: item_from,
|
||||
to: item_to,
|
||||
tx,
|
||||
}))
|
||||
.await;
|
||||
rx.recv()
|
||||
.await
|
||||
.unwrap_or(fs_extra::dir::TransitProcessResult::Abort)
|
||||
})
|
||||
}
|
||||
fs_extra::dir::TransitState::NoAccess => {
|
||||
//TODO: permission error dialog
|
||||
fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
|
|
|
|||
60
src/tab.rs
60
src/tab.rs
|
|
@ -269,6 +269,25 @@ pub fn item_from_entry(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn item_from_path<P: Into<PathBuf>>(path: P, sizes: IconSizes) -> Result<Item, String> {
|
||||
let path = path.into();
|
||||
let name_os = path
|
||||
.file_name()
|
||||
.ok_or_else(|| format!("failed to get file name from path {:?}", path))?;
|
||||
let name = name_os
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"failed to parse file name for {:?}: {:?} is not valid UTF-8",
|
||||
path, name_os
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
let metadata = fs::metadata(&path)
|
||||
.map_err(|err| format!("failed to read metadata for {:?}: {}", path, err))?;
|
||||
Ok(item_from_entry(path, name, metadata, sizes))
|
||||
}
|
||||
|
||||
pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
||||
let mut items = Vec::new();
|
||||
match fs::read_dir(tab_path) {
|
||||
|
|
@ -788,6 +807,47 @@ impl Item {
|
|||
|
||||
column.into()
|
||||
}
|
||||
|
||||
pub fn replace_view(
|
||||
&self,
|
||||
heading: String,
|
||||
sizes: IconSizes,
|
||||
) -> Element<'static, app::Message> {
|
||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
let mut row = widget::row().spacing(space_xxxs);
|
||||
row = row.push(self.preview(sizes));
|
||||
|
||||
let mut column = widget::column().spacing(space_xxxs);
|
||||
column = column.push(widget::text::heading(heading));
|
||||
|
||||
//TODO: translate!
|
||||
//TODO: correct display of folder size?
|
||||
match &self.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
if metadata.is_dir() {
|
||||
column = column.push(widget::text(format!("Items: {}", children)));
|
||||
} else {
|
||||
column = column.push(widget::text(format!(
|
||||
"Size: {}",
|
||||
format_size(metadata.len())
|
||||
)));
|
||||
}
|
||||
if let Ok(time) = metadata.modified() {
|
||||
column = column.push(widget::text(format!(
|
||||
"Last modified: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format(TIME_FORMAT)
|
||||
)));
|
||||
}
|
||||
}
|
||||
ItemMetadata::Trash { .. } => {
|
||||
//TODO: trash metadata
|
||||
}
|
||||
}
|
||||
|
||||
row = row.push(column);
|
||||
row.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue