Implement network drive connection, part of #202

This commit is contained in:
Jeremy Soller 2024-09-12 15:54:54 -06:00
parent f41730978c
commit 0d8fd00dd3
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
8 changed files with 542 additions and 86 deletions

View file

@ -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

View file

@ -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)]