Implement network drive connection, part of #202
This commit is contained in:
parent
f41730978c
commit
0d8fd00dd3
8 changed files with 542 additions and 86 deletions
|
|
@ -6,7 +6,7 @@ use gio::{glib, prelude::*};
|
|||
use std::{any::TypeId, future::pending, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
|
||||
use super::{Mounter, MounterItem, MounterItems};
|
||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||
|
||||
fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
||||
if let Some(themed_icon) = icon.downcast_ref::<gio::ThemedIcon>() {
|
||||
|
|
@ -24,12 +24,15 @@ fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
|||
enum Cmd {
|
||||
Rescan,
|
||||
Mount(MounterItem),
|
||||
NetworkDrive(String),
|
||||
Unmount(MounterItem),
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Changed,
|
||||
Items(MounterItems),
|
||||
NetworkAuth(String, MounterAuth, mpsc::Sender<MounterAuth>),
|
||||
NetworkResult(String, Result<bool, String>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -190,6 +193,80 @@ impl Gvfs {
|
|||
);
|
||||
}
|
||||
}
|
||||
Cmd::NetworkDrive(uri) => {
|
||||
let mount_op = gio::MountOperation::new();
|
||||
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
let uri = uri.clone();
|
||||
mount_op.connect_ask_password(move |mount_op, message, default_user, default_domain, flags| {
|
||||
let auth = MounterAuth {
|
||||
message: message.to_string(),
|
||||
username_opt: if flags.contains(gio::AskPasswordFlags::NEED_USERNAME) {
|
||||
Some(default_user.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
domain_opt: if flags.contains(gio::AskPasswordFlags::NEED_DOMAIN) {
|
||||
Some(default_domain.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
password_opt: if flags.contains(gio::AskPasswordFlags::NEED_PASSWORD) {
|
||||
Some(String::new())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
remember_opt: if flags.contains(gio::AskPasswordFlags::SAVING_SUPPORTED) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
anonymous_opt: if flags.contains(gio::AskPasswordFlags::ANONYMOUS_SUPPORTED) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
||||
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_tx)).unwrap();
|
||||
//TODO: async recv?
|
||||
if let Some(auth) = auth_rx.blocking_recv() {
|
||||
if auth.anonymous_opt == Some(true) {
|
||||
mount_op.set_anonymous(true);
|
||||
} else {
|
||||
mount_op.set_username(auth.username_opt.as_deref());
|
||||
mount_op.set_domain(auth.domain_opt.as_deref());
|
||||
mount_op.set_password(auth.password_opt.as_deref());
|
||||
if auth.remember_opt == Some(true) {
|
||||
mount_op.set_password_save(gio::PasswordSave::Permanently);
|
||||
}
|
||||
}
|
||||
mount_op.reply(gio::MountOperationResult::Handled);
|
||||
} else {
|
||||
mount_op.reply(gio::MountOperationResult::Aborted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let file = gio::File::for_uri(&uri);
|
||||
let event_tx = event_tx.clone();
|
||||
file.mount_enclosing_volume(
|
||||
gio::MountMountFlags::empty(),
|
||||
Some(&mount_op),
|
||||
gio::Cancellable::NONE,
|
||||
move |res| {
|
||||
log::info!("network drive {}: result {:?}", uri, res);
|
||||
event_tx.send(Event::NetworkResult(uri, match res {
|
||||
Ok(()) => Ok(true),
|
||||
Err(err) => match err.kind::<gio::IOErrorEnum>() {
|
||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{}", err))
|
||||
}
|
||||
})).unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
Cmd::Unmount(mounter_item) => {
|
||||
let MounterItem::Gvfs(item) = mounter_item else { continue };
|
||||
let ItemKind::Mount = item.kind else { continue };
|
||||
|
|
@ -242,6 +319,17 @@ impl Mounter for Gvfs {
|
|||
)
|
||||
}
|
||||
|
||||
fn network_drive(&self, uri: String) -> Command<()> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
command_tx.send(Cmd::NetworkDrive(uri)).unwrap();
|
||||
()
|
||||
},
|
||||
|x| x,
|
||||
)
|
||||
}
|
||||
|
||||
fn unmount(&self, item: MounterItem) -> Command<()> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
Command::perform(
|
||||
|
|
@ -253,17 +341,23 @@ impl Mounter for Gvfs {
|
|||
)
|
||||
}
|
||||
|
||||
fn subscription(&self) -> subscription::Subscription<MounterItems> {
|
||||
fn subscription(&self) -> subscription::Subscription<MounterMessage> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
let event_rx = self.event_rx.clone();
|
||||
subscription::channel(TypeId::of::<Self>(), 1, |mut output| async move {
|
||||
command_tx.send(Cmd::Rescan).unwrap();
|
||||
while let Some(event) = event_rx.lock().await.recv().await {
|
||||
match event {
|
||||
Event::Changed => {
|
||||
command_tx.send(Cmd::Rescan).unwrap();
|
||||
}
|
||||
Event::Items(items) => output.send(items).await.unwrap(),
|
||||
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
|
||||
Event::Items(items) => output.send(MounterMessage::Items(items)).await.unwrap(),
|
||||
Event::NetworkAuth(uri, auth, auth_tx) => output
|
||||
.send(MounterMessage::NetworkAuth(uri, auth, auth_tx))
|
||||
.await
|
||||
.unwrap(),
|
||||
Event::NetworkResult(uri, res) => output
|
||||
.send(MounterMessage::NetworkResult(uri, res))
|
||||
.await
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
pending().await
|
||||
|
|
|
|||
|
|
@ -1,9 +1,40 @@
|
|||
use cosmic::{iced::subscription, widget, Command};
|
||||
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[cfg(feature = "gvfs")]
|
||||
mod gvfs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MounterAuth {
|
||||
pub message: String,
|
||||
pub username_opt: Option<String>,
|
||||
pub domain_opt: Option<String>,
|
||||
pub password_opt: Option<String>,
|
||||
pub remember_opt: Option<bool>,
|
||||
pub anonymous_opt: Option<bool>,
|
||||
}
|
||||
|
||||
// Custom debug for MounterAuth to hide password
|
||||
impl fmt::Debug for MounterAuth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MounterAuth")
|
||||
.field("username_opt", &self.username_opt)
|
||||
.field("domain_opt", &self.domain_opt)
|
||||
.field(
|
||||
"password_opt",
|
||||
if self.password_opt.is_some() {
|
||||
&"Some(*)"
|
||||
} else {
|
||||
&"None"
|
||||
},
|
||||
)
|
||||
.field("remember_opt", &self.remember_opt)
|
||||
.field("anonymous_opt", &self.anonymous_opt)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MounterItem {
|
||||
#[cfg(feature = "gvfs")]
|
||||
|
|
@ -48,11 +79,19 @@ impl MounterItem {
|
|||
|
||||
pub type MounterItems = Vec<MounterItem>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MounterMessage {
|
||||
Items(MounterItems),
|
||||
NetworkAuth(String, MounterAuth, mpsc::Sender<MounterAuth>),
|
||||
NetworkResult(String, Result<bool, String>),
|
||||
}
|
||||
|
||||
pub trait Mounter {
|
||||
//TODO: send result
|
||||
fn mount(&self, item: MounterItem) -> Command<()>;
|
||||
fn network_drive(&self, uri: String) -> Command<()>;
|
||||
fn unmount(&self, item: MounterItem) -> Command<()>;
|
||||
fn subscription(&self) -> subscription::Subscription<MounterItems>;
|
||||
fn subscription(&self) -> subscription::Subscription<MounterMessage>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue