2024-04-22 13:14:25 -06:00
|
|
|
use cosmic::{
|
2025-09-03 23:24:38 +02:00
|
|
|
Task,
|
|
|
|
|
iced::{Subscription, futures::SinkExt, stream},
|
|
|
|
|
widget,
|
2024-04-22 13:14:25 -06:00
|
|
|
};
|
|
|
|
|
use gio::{glib, prelude::*};
|
2026-03-13 16:04:17 -04:00
|
|
|
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
2026-04-14 16:38:56 +02:00
|
|
|
use tokio::sync::mpsc;
|
2024-04-22 09:54:00 -06:00
|
|
|
|
2024-09-12 15:54:54 -06:00
|
|
|
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
2024-09-13 15:13:37 -06:00
|
|
|
use crate::{
|
|
|
|
|
config::IconSizes,
|
|
|
|
|
err_str,
|
2024-11-15 17:30:25 -07:00
|
|
|
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
|
2024-09-13 15:13:37 -06:00
|
|
|
};
|
2024-04-22 09:54:00 -06:00
|
|
|
|
2025-07-15 22:07:28 -04:00
|
|
|
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
|
|
|
|
|
|
2025-11-22 12:29:02 +10:00
|
|
|
fn resolve_uri(uri: &str) -> (String, gio::File) {
|
|
|
|
|
let file = gio::File::for_uri(uri);
|
|
|
|
|
// Resolve the target-uri if it exists
|
|
|
|
|
if let Ok(file_info) = file.query_info(
|
|
|
|
|
TARGET_URI_ATTRIBUTE,
|
|
|
|
|
gio::FileQueryInfoFlags::NONE,
|
|
|
|
|
gio::Cancellable::NONE,
|
2026-01-24 17:03:31 +01:00
|
|
|
) && let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE)
|
|
|
|
|
{
|
|
|
|
|
let resolved_uri = String::from(resolved_uri);
|
|
|
|
|
let file = gio::File::for_uri(&resolved_uri);
|
|
|
|
|
return (resolved_uri, file);
|
2025-11-22 12:29:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(uri.to_string(), file)
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-22 13:14:25 -06:00
|
|
|
fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
|
|
|
|
if let Some(themed_icon) = icon.downcast_ref::<gio::ThemedIcon>() {
|
|
|
|
|
for name in themed_icon.names() {
|
|
|
|
|
let named = widget::icon::from_name(name.as_str()).size(size);
|
|
|
|
|
if let Some(path) = named.path() {
|
|
|
|
|
return Some(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//TODO: handle more gio icon types
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 16:28:30 -06:00
|
|
|
fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems {
|
2025-10-28 13:10:40 +10:00
|
|
|
let mut items: MounterItems = (monitor.mounts().into_iter())
|
|
|
|
|
.enumerate()
|
2025-12-29 17:12:23 -07:00
|
|
|
// Hide shadowed mounts
|
|
|
|
|
.filter(|(_, mount)| !mount.is_shadowed())
|
2025-10-28 13:10:40 +10:00
|
|
|
.map(|(i, mount)| {
|
2025-11-17 08:58:28 +01:00
|
|
|
let root = MountExt::root(&mount);
|
|
|
|
|
let is_remote = root
|
|
|
|
|
.query_filesystem_info(
|
|
|
|
|
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
)
|
|
|
|
|
.ok()
|
2026-01-24 17:03:31 +01:00
|
|
|
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
2025-11-17 08:58:28 +01:00
|
|
|
.unwrap_or(true); // Default to remote if query fails
|
|
|
|
|
|
2025-10-28 13:10:40 +10:00
|
|
|
MounterItem::Gvfs(Item {
|
|
|
|
|
uri: mount.root().uri().into(),
|
|
|
|
|
kind: ItemKind::Mount,
|
|
|
|
|
index: i,
|
|
|
|
|
name: mount.name().into(),
|
|
|
|
|
is_mounted: true,
|
2025-11-17 08:58:28 +01:00
|
|
|
is_remote,
|
2025-10-28 13:10:40 +10:00
|
|
|
icon_opt: gio_icon_to_path(&MountExt::icon(&mount), sizes.grid()),
|
|
|
|
|
icon_symbolic_opt: gio_icon_to_path(&MountExt::symbolic_icon(&mount), 16),
|
2025-11-17 08:58:28 +01:00
|
|
|
path_opt: root.path(),
|
2025-10-28 13:10:40 +10:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
items.extend(
|
|
|
|
|
(monitor.volumes().into_iter())
|
|
|
|
|
.enumerate()
|
2024-10-04 16:28:30 -06:00
|
|
|
// Volumes with mounts are already listed by mount
|
2025-10-28 13:10:40 +10:00
|
|
|
.filter(|(_, volume)| volume.get_mount().is_none())
|
|
|
|
|
.map(|(i, volume)| {
|
|
|
|
|
let uri = VolumeExt::activation_root(&volume)
|
|
|
|
|
.map(|f| f.uri().into())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
MounterItem::Gvfs(Item {
|
|
|
|
|
// TODO can we get URI for volumes with no mount?
|
|
|
|
|
uri,
|
|
|
|
|
kind: ItemKind::Volume,
|
|
|
|
|
index: i,
|
|
|
|
|
name: volume.name().into(),
|
|
|
|
|
is_mounted: false,
|
2025-11-17 08:58:28 +01:00
|
|
|
is_remote: false,
|
2025-10-28 13:10:40 +10:00
|
|
|
icon_opt: gio_icon_to_path(&VolumeExt::icon(&volume), sizes.grid()),
|
|
|
|
|
icon_symbolic_opt: gio_icon_to_path(&VolumeExt::symbolic_icon(&volume), 16),
|
|
|
|
|
path_opt: None,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
);
|
2024-10-04 16:28:30 -06:00
|
|
|
items
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 18:10:14 -04:00
|
|
|
fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
|
2025-07-15 22:07:28 -04:00
|
|
|
let force_dir = uri.starts_with("network:///");
|
2025-11-22 12:29:02 +10:00
|
|
|
let (_, file) = resolve_uri(uri);
|
2025-07-15 22:07:28 -04:00
|
|
|
|
2025-11-12 23:12:12 +01:00
|
|
|
// Read .hidden file if present
|
|
|
|
|
let hidden_files: Box<[String]> = if let Some(path) = file.path() {
|
|
|
|
|
let hidden_file_path = path.join(".hidden");
|
|
|
|
|
if hidden_file_path.is_file() {
|
|
|
|
|
tab::parse_hidden_file(&hidden_file_path)
|
|
|
|
|
} else {
|
|
|
|
|
Box::from([])
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Box::from([])
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-13 15:13:37 -06:00
|
|
|
let mut items = Vec::new();
|
|
|
|
|
for info_res in file
|
|
|
|
|
.enumerate_children("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE)
|
|
|
|
|
.map_err(err_str)?
|
|
|
|
|
{
|
|
|
|
|
let info = info_res.map_err(err_str)?;
|
2025-10-28 13:10:40 +10:00
|
|
|
let name = info.name().to_string_lossy().into_owned();
|
|
|
|
|
let display_name = String::from(info.display_name());
|
2024-09-13 15:13:37 -06:00
|
|
|
|
2025-10-28 13:10:40 +10:00
|
|
|
let uri = String::from(file.child(info.name()).uri());
|
2025-07-15 22:07:28 -04:00
|
|
|
|
2024-09-13 15:13:37 -06:00
|
|
|
//TODO: what is the best way to resolve shortcuts?
|
2025-07-15 22:07:28 -04:00
|
|
|
let location = Location::Network(uri, display_name.clone(), file.child(&name).path());
|
|
|
|
|
|
|
|
|
|
let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) {
|
|
|
|
|
let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED);
|
2025-10-03 03:24:44 +02:00
|
|
|
let is_dir = matches!(info.file_type(), gio::FileType::Directory);
|
2025-10-28 13:10:40 +10:00
|
|
|
let size_opt = (!is_dir).then_some(info.size() as u64);
|
2025-07-15 22:07:28 -04:00
|
|
|
let mut children_opt = None;
|
|
|
|
|
|
|
|
|
|
if is_dir {
|
|
|
|
|
if let Some(path) = file.child(&name).path() {
|
|
|
|
|
//TODO: calculate children in the background (and make it cancellable?)
|
|
|
|
|
match std::fs::read_dir(&path) {
|
|
|
|
|
Ok(entries) => {
|
|
|
|
|
children_opt = Some(entries.count());
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::warn!("failed to read directory {}: {}", path.display(), err);
|
2025-07-16 18:55:23 -04:00
|
|
|
children_opt = Some(0);
|
2025-07-15 22:07:28 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-16 18:55:23 -04:00
|
|
|
} else {
|
|
|
|
|
children_opt = Some(0);
|
2025-07-15 22:07:28 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ItemMetadata::GvfsPath {
|
|
|
|
|
mtime,
|
|
|
|
|
size_opt,
|
|
|
|
|
children_opt,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ItemMetadata::SimpleDir { entries: 0 }
|
|
|
|
|
};
|
2024-09-13 15:13:37 -06:00
|
|
|
|
|
|
|
|
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
|
|
|
|
let file_icon = |size| {
|
|
|
|
|
info.icon()
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|icon| gio_icon_to_path(icon, size))
|
2025-01-20 01:48:41 -05:00
|
|
|
.map(widget::icon::from_path)
|
2024-09-13 15:13:37 -06:00
|
|
|
.unwrap_or(
|
|
|
|
|
widget::icon::from_name(if metadata.is_dir() {
|
|
|
|
|
"folder"
|
|
|
|
|
} else {
|
|
|
|
|
"text-x-generic"
|
|
|
|
|
})
|
|
|
|
|
.size(size)
|
|
|
|
|
.handle(),
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
(
|
|
|
|
|
//TODO: get mime from content_type?
|
|
|
|
|
"inode/directory".parse().unwrap(),
|
|
|
|
|
file_icon(sizes.grid()),
|
|
|
|
|
file_icon(sizes.list()),
|
|
|
|
|
file_icon(sizes.list_condensed()),
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-12 23:12:12 +01:00
|
|
|
// Check if item is hidden
|
|
|
|
|
let hidden = name.starts_with('.')
|
|
|
|
|
|| info.boolean(gio::FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)
|
|
|
|
|
|| hidden_files.contains(&name);
|
|
|
|
|
|
2024-09-13 15:13:37 -06:00
|
|
|
items.push(tab::Item {
|
|
|
|
|
name,
|
2025-06-30 19:23:40 -04:00
|
|
|
is_mount_point: false,
|
2024-09-13 15:13:37 -06:00
|
|
|
display_name,
|
|
|
|
|
metadata,
|
2025-11-12 23:12:12 +01:00
|
|
|
hidden,
|
2024-09-13 15:13:37 -06:00
|
|
|
location_opt: Some(location),
|
2026-04-14 16:23:10 +02:00
|
|
|
image_dimensions: None,
|
2024-09-13 15:13:37 -06:00
|
|
|
mime,
|
|
|
|
|
icon_handle_grid,
|
|
|
|
|
icon_handle_list,
|
|
|
|
|
icon_handle_list_condensed,
|
|
|
|
|
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
|
|
|
|
button_id: widget::Id::unique(),
|
|
|
|
|
pos_opt: Cell::new(None),
|
|
|
|
|
rect_opt: Cell::new(None),
|
|
|
|
|
selected: false,
|
2024-10-16 10:22:25 +11:00
|
|
|
highlighted: false,
|
2024-09-13 15:13:37 -06:00
|
|
|
overlaps_drag_rect: false,
|
2024-11-15 17:30:25 -07:00
|
|
|
//TODO: scan directory size on gvfs mounts?
|
|
|
|
|
dir_size: DirSize::NotDirectory,
|
2025-04-28 23:35:27 +10:00
|
|
|
cut: false,
|
2024-09-13 15:13:37 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Ok(items)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-07 09:53:22 +10:00
|
|
|
fn dir_info(uri: &str) -> Result<(String, String, Option<PathBuf>), glib::Error> {
|
2025-11-22 12:29:02 +10:00
|
|
|
let (resolved_uri, file) = resolve_uri(uri);
|
|
|
|
|
let info = file.query_info(
|
|
|
|
|
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
|
|
|
|
gio::FileQueryInfoFlags::NONE,
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
)?;
|
|
|
|
|
|
2025-12-07 09:53:22 +10:00
|
|
|
Ok((resolved_uri, info.display_name().into(), file.path()))
|
2025-11-22 12:29:02 +10:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 16:38:56 +02:00
|
|
|
fn mount_op(
|
|
|
|
|
uri: String,
|
|
|
|
|
event_tx: std::sync::Weak<crate::channel::Sender<Event>>,
|
|
|
|
|
) -> gio::MountOperation {
|
2024-09-16 09:09:21 -06:00
|
|
|
let mount_op = gio::MountOperation::new();
|
|
|
|
|
mount_op.connect_ask_password(
|
|
|
|
|
move |mount_op, message, default_user, default_domain, flags| {
|
|
|
|
|
let auth = MounterAuth {
|
|
|
|
|
message: message.to_string(),
|
2025-10-28 13:10:40 +10:00
|
|
|
username_opt: flags
|
|
|
|
|
.contains(gio::AskPasswordFlags::NEED_USERNAME)
|
|
|
|
|
.then(|| default_user.to_string()),
|
|
|
|
|
domain_opt: flags
|
|
|
|
|
.contains(gio::AskPasswordFlags::NEED_DOMAIN)
|
|
|
|
|
.then(|| default_domain.to_string()),
|
|
|
|
|
password_opt: flags
|
|
|
|
|
.contains(gio::AskPasswordFlags::NEED_PASSWORD)
|
|
|
|
|
.then(String::new),
|
|
|
|
|
remember_opt: flags
|
|
|
|
|
.contains(gio::AskPasswordFlags::SAVING_SUPPORTED)
|
|
|
|
|
.then_some(false),
|
|
|
|
|
anonymous_opt: flags
|
|
|
|
|
.contains(gio::AskPasswordFlags::ANONYMOUS_SUPPORTED)
|
|
|
|
|
.then_some(false),
|
2024-09-16 09:09:21 -06:00
|
|
|
};
|
|
|
|
|
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_tx));
|
|
|
|
|
}
|
2024-09-16 09:09:21 -06:00
|
|
|
//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);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
mount_op
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-22 13:14:25 -06:00
|
|
|
enum Cmd {
|
2024-10-04 16:28:30 -06:00
|
|
|
Items(IconSizes, mpsc::Sender<MounterItems>),
|
2024-04-22 13:14:25 -06:00
|
|
|
Rescan,
|
2025-07-12 19:42:04 -04:00
|
|
|
Mount(
|
|
|
|
|
MounterItem,
|
|
|
|
|
tokio::sync::oneshot::Sender<anyhow::Result<()>>,
|
|
|
|
|
),
|
|
|
|
|
NetworkDrive(String, tokio::sync::oneshot::Sender<anyhow::Result<()>>),
|
2024-09-13 15:13:37 -06:00
|
|
|
NetworkScan(
|
|
|
|
|
String,
|
|
|
|
|
IconSizes,
|
|
|
|
|
mpsc::Sender<Result<Vec<tab::Item>, String>>,
|
|
|
|
|
),
|
2025-12-07 09:53:22 +10:00
|
|
|
DirInfo(
|
|
|
|
|
String,
|
|
|
|
|
mpsc::Sender<Result<(String, String, Option<PathBuf>), glib::Error>>,
|
|
|
|
|
),
|
2024-04-24 13:59:55 -06:00
|
|
|
Unmount(MounterItem),
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Event {
|
|
|
|
|
Changed,
|
|
|
|
|
Items(MounterItems),
|
2024-10-11 16:43:16 -06:00
|
|
|
MountResult(MounterItem, Result<bool, String>),
|
2024-09-12 15:54:54 -06:00
|
|
|
NetworkAuth(String, MounterAuth, mpsc::Sender<MounterAuth>),
|
|
|
|
|
NetworkResult(String, Result<bool, String>),
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum ItemKind {
|
|
|
|
|
Mount,
|
|
|
|
|
Volume,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: better method of matching items
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct Item {
|
2025-07-12 19:42:04 -04:00
|
|
|
uri: String,
|
2024-04-22 13:14:25 -06:00
|
|
|
kind: ItemKind,
|
|
|
|
|
index: usize,
|
|
|
|
|
name: String,
|
|
|
|
|
is_mounted: bool,
|
2025-11-17 08:58:28 +01:00
|
|
|
is_remote: bool,
|
2024-04-22 13:14:25 -06:00
|
|
|
icon_opt: Option<PathBuf>,
|
2024-10-04 16:28:30 -06:00
|
|
|
icon_symbolic_opt: Option<PathBuf>,
|
2024-04-22 13:14:25 -06:00
|
|
|
path_opt: Option<PathBuf>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Item {
|
|
|
|
|
pub fn name(&self) -> String {
|
|
|
|
|
self.name.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 16:20:51 +10:00
|
|
|
pub const fn is_mounted(&self) -> bool {
|
2024-04-22 13:14:25 -06:00
|
|
|
self.is_mounted
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 08:58:28 +01:00
|
|
|
pub const fn is_remote(&self) -> bool {
|
|
|
|
|
self.is_remote
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:42:04 -04:00
|
|
|
pub fn uri(&self) -> String {
|
|
|
|
|
self.uri.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 16:28:30 -06:00
|
|
|
pub fn icon(&self, symbolic: bool) -> Option<widget::icon::Handle> {
|
|
|
|
|
if symbolic {
|
|
|
|
|
self.icon_symbolic_opt.as_ref()
|
|
|
|
|
} else {
|
|
|
|
|
self.icon_opt.as_ref()
|
|
|
|
|
}
|
|
|
|
|
.map(|icon| widget::icon::from_path(icon.clone()))
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn path(&self) -> Option<PathBuf> {
|
|
|
|
|
self.path_opt.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-22 09:54:00 -06:00
|
|
|
pub struct Gvfs {
|
2024-04-22 13:14:25 -06:00
|
|
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
2026-04-14 16:38:56 +02:00
|
|
|
event_rx: Arc<crate::channel::Receiver<Event>>,
|
2024-04-22 09:54:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Gvfs {
|
|
|
|
|
pub fn new() -> Self {
|
2024-04-22 13:14:25 -06:00
|
|
|
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
|
|
|
|
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
|
2026-04-14 16:38:56 +02:00
|
|
|
let (event_tx, event_rx) = crate::channel::channel();
|
|
|
|
|
let event_tx = Arc::new(event_tx);
|
2024-04-22 13:14:25 -06:00
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let main_loop = glib::MainLoop::new(None, false);
|
|
|
|
|
main_loop.context().spawn_local(async move {
|
2026-04-14 16:38:56 +02:00
|
|
|
let event_tx = Arc::downgrade(&event_tx);
|
2024-04-22 13:14:25 -06:00
|
|
|
let monitor = gio::VolumeMonitor::get();
|
|
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_mount_changed(move |_monitor, mount| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("mount changed {}", MountExt::name(mount));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_mount_added(move |_monitor, mount| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("mount added {}", MountExt::name(mount));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_mount_removed(move |_monitor, mount| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("mount removed {}", MountExt::name(mount));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
2024-04-22 09:54:00 -06:00
|
|
|
|
2024-04-22 13:14:25 -06:00
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_volume_changed(move |_monitor, volume| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("volume changed {}", VolumeExt::name(volume));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_volume_added(move |_monitor, volume| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("volume added {}", VolumeExt::name(volume));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
monitor.connect_volume_removed(move |_monitor, volume| {
|
2024-05-31 10:39:27 -06:00
|
|
|
log::info!("volume removed {}", VolumeExt::name(volume));
|
2026-04-14 16:38:56 +02:00
|
|
|
if let Some(event_tx) = event_tx.upgrade() {
|
|
|
|
|
event_tx.send(Event::Changed);
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while let Some(command) = command_rx.recv().await {
|
|
|
|
|
match command {
|
2024-10-04 16:28:30 -06:00
|
|
|
Cmd::Items(sizes, items_tx) => {
|
|
|
|
|
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
Cmd::Rescan => {
|
2026-04-14 16:38:56 +02:00
|
|
|
let Some(event_tx) = event_tx.upgrade() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
event_tx.send(Event::Items(items(&monitor, IconSizes::default())));
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
2025-07-12 19:42:04 -04:00
|
|
|
Cmd::Mount(mounter_item, complete_tx) => {
|
|
|
|
|
let MounterItem::Gvfs(ref item) = mounter_item else {
|
|
|
|
|
_ = complete_tx.send(Err(anyhow::anyhow!("No mounter item")));
|
|
|
|
|
continue
|
|
|
|
|
};
|
|
|
|
|
let ItemKind::Volume = item.kind else {
|
|
|
|
|
_ = complete_tx.send(Err(anyhow::anyhow!("No mounter volume")));
|
|
|
|
|
continue
|
|
|
|
|
};
|
2024-04-22 13:14:25 -06:00
|
|
|
for (i, volume) in monitor.volumes().into_iter().enumerate() {
|
|
|
|
|
if i != item.index {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let name = VolumeExt::name(&volume);
|
|
|
|
|
if item.name != name {
|
|
|
|
|
log::warn!("trying to mount volume {} failed: name is {:?} when {:?} was expected", i, name, item.name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("mount {name}");
|
2024-10-11 16:43:16 -06:00
|
|
|
//TODO: do not use name as a URI for mount_op
|
|
|
|
|
let mount_op = mount_op(name.to_string(), event_tx.clone());
|
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
let mounter_item = mounter_item.clone();
|
2025-11-17 09:06:02 +01:00
|
|
|
let volume_for_callback = volume.clone();
|
2024-04-22 13:14:25 -06:00
|
|
|
VolumeExt::mount(
|
|
|
|
|
&volume,
|
|
|
|
|
gio::MountMountFlags::NONE,
|
2024-10-11 16:43:16 -06:00
|
|
|
Some(&mount_op),
|
2024-04-22 13:14:25 -06:00
|
|
|
gio::Cancellable::NONE,
|
2024-10-11 16:43:16 -06:00
|
|
|
move |res| {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("mount {name}: result {res:?}");
|
2025-11-17 09:06:02 +01:00
|
|
|
// Update the mounter_item with mount information after successful mount
|
|
|
|
|
let mut updated_item = mounter_item.clone();
|
2026-01-24 17:03:31 +01:00
|
|
|
if res.is_ok()
|
|
|
|
|
&& let MounterItem::Gvfs(ref mut item) = updated_item
|
|
|
|
|
&& let Some(mount) = volume_for_callback.get_mount() {
|
2025-11-17 09:06:02 +01:00
|
|
|
let root = MountExt::root(&mount);
|
|
|
|
|
item.path_opt = root.path();
|
|
|
|
|
item.is_mounted = true;
|
|
|
|
|
// Query if remote
|
|
|
|
|
item.is_remote = root
|
|
|
|
|
.query_filesystem_info(
|
|
|
|
|
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
)
|
2026-01-24 17:03:31 +01:00
|
|
|
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
2025-11-17 09:06:02 +01:00
|
|
|
.unwrap_or(true);
|
|
|
|
|
}
|
2026-04-14 16:38:56 +02:00
|
|
|
let Some(event_tx) = event_tx.upgrade() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2025-11-17 09:06:02 +01:00
|
|
|
event_tx.send(Event::MountResult(updated_item, match res {
|
2025-07-12 19:42:04 -04:00
|
|
|
Ok(()) => {
|
|
|
|
|
_ = complete_tx.send(Ok(()));
|
|
|
|
|
Ok(true)
|
|
|
|
|
},
|
|
|
|
|
Err(err) => {
|
|
|
|
|
_ = complete_tx.send(Err(anyhow::anyhow!("{err:?}")));
|
|
|
|
|
match err.kind::<gio::IOErrorEnum>() {
|
2024-10-11 16:43:16 -06:00
|
|
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
2025-10-26 16:20:51 +10:00
|
|
|
_ => Err(format!("{err}"))
|
2025-07-12 19:42:04 -04:00
|
|
|
}}
|
2026-04-14 16:38:56 +02:00
|
|
|
}));
|
2024-04-22 13:14:25 -06:00
|
|
|
},
|
|
|
|
|
);
|
2025-07-12 19:42:04 -04:00
|
|
|
break;
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-12 19:42:04 -04:00
|
|
|
Cmd::NetworkDrive(uri, result_tx) => {
|
2024-09-12 15:54:54 -06:00
|
|
|
let file = gio::File::for_uri(&uri);
|
2024-09-16 09:09:21 -06:00
|
|
|
let mount_op = mount_op(uri.clone(), event_tx.clone());
|
2024-09-12 15:54:54 -06:00
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
file.mount_enclosing_volume(
|
2024-10-11 16:43:16 -06:00
|
|
|
gio::MountMountFlags::NONE,
|
2024-09-12 15:54:54 -06:00
|
|
|
Some(&mount_op),
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
move |res| {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("network drive {uri}: result {res:?}");
|
2026-04-14 16:38:56 +02:00
|
|
|
let Some(event_tx) = event_tx.upgrade() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2024-09-12 15:54:54 -06:00
|
|
|
event_tx.send(Event::NetworkResult(uri, match res {
|
2025-07-12 19:42:04 -04:00
|
|
|
Ok(()) => {
|
|
|
|
|
_ = result_tx.send(Ok(()));
|
|
|
|
|
Ok(true)},
|
|
|
|
|
Err(err) => {
|
|
|
|
|
_ = result_tx.send(Err(anyhow::anyhow!("{err:?}")));
|
|
|
|
|
match err.kind::<gio::IOErrorEnum>() {
|
2024-09-12 15:54:54 -06:00
|
|
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
2025-10-26 16:20:51 +10:00
|
|
|
_ => Err(format!("{err}"))
|
2025-07-12 19:42:04 -04:00
|
|
|
}}
|
2026-04-14 16:38:56 +02:00
|
|
|
}));
|
2024-09-12 15:54:54 -06:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-22 12:29:02 +10:00
|
|
|
Cmd::NetworkScan(uri, sizes, items_tx) => {
|
|
|
|
|
let (resolved_uri, file) = resolve_uri(&uri);
|
2025-07-15 22:07:28 -04:00
|
|
|
|
2025-11-22 12:29:02 +10:00
|
|
|
let needs_mount = resolved_uri != "network:///" && match file.find_enclosing_mount(gio::Cancellable::NONE) {
|
2024-09-16 09:09:21 -06:00
|
|
|
Ok(_) => false,
|
2025-01-20 01:48:41 -05:00
|
|
|
Err(err) => matches!(err.kind::<gio::IOErrorEnum>(), Some(gio::IOErrorEnum::NotMounted))
|
2024-09-16 09:09:21 -06:00
|
|
|
};
|
2025-07-15 22:07:28 -04:00
|
|
|
|
2024-09-16 09:09:21 -06:00
|
|
|
if needs_mount {
|
2025-11-22 12:29:02 +10:00
|
|
|
let mount_op = mount_op(resolved_uri.clone(), event_tx.clone());
|
2024-09-16 09:09:21 -06:00
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
|
file.mount_enclosing_volume(
|
|
|
|
|
gio::MountMountFlags::empty(),
|
|
|
|
|
Some(&mount_op),
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
move |res| {
|
2025-11-22 12:29:02 +10:00
|
|
|
log::info!("network scan mounted {resolved_uri}: result {res:?}");
|
2025-07-15 22:07:28 -04:00
|
|
|
// FIXME sometimes a uri can be mounted and then not recognized as mounted...
|
|
|
|
|
// seems to be related to uri with a path
|
2025-11-22 12:29:02 +10:00
|
|
|
items_tx.blocking_send(network_scan(&uri, sizes)).unwrap();
|
2026-04-14 16:38:56 +02:00
|
|
|
let Some(event_tx) = event_tx.upgrade() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2025-11-22 12:29:02 +10:00
|
|
|
event_tx.send(Event::NetworkResult(resolved_uri, match res {
|
2024-09-16 09:09:21 -06:00
|
|
|
Ok(()) => {
|
|
|
|
|
Ok(true)
|
|
|
|
|
},
|
|
|
|
|
Err(err) => match err.kind::<gio::IOErrorEnum>() {
|
|
|
|
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
2025-10-26 16:20:51 +10:00
|
|
|
_ => Err(format!("{err}"))
|
2024-09-16 09:09:21 -06:00
|
|
|
}
|
2026-04-14 16:38:56 +02:00
|
|
|
}));
|
2024-09-16 09:09:21 -06:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2025-11-22 12:29:02 +10:00
|
|
|
items_tx.send(network_scan(&uri, sizes)).await.unwrap();
|
2024-09-16 09:09:21 -06:00
|
|
|
}
|
2024-09-13 15:13:37 -06:00
|
|
|
}
|
2025-11-22 12:29:02 +10:00
|
|
|
Cmd::DirInfo(uri, result_tx) => {
|
|
|
|
|
result_tx.send(dir_info(&uri)).await.unwrap();
|
|
|
|
|
}
|
2024-04-24 13:59:55 -06:00
|
|
|
Cmd::Unmount(mounter_item) => {
|
|
|
|
|
let MounterItem::Gvfs(item) = mounter_item else { continue };
|
|
|
|
|
let ItemKind::Mount = item.kind else { continue };
|
|
|
|
|
for (i, mount) in monitor.mounts().into_iter().enumerate() {
|
|
|
|
|
if i != item.index {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let name = MountExt::name(&mount);
|
|
|
|
|
if item.name != name {
|
|
|
|
|
log::warn!("trying to unmount mount {} failed: name is {:?} when {:?} was expected", i, name, item.name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 13:21:02 -06:00
|
|
|
if MountExt::can_eject(&mount) {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("eject {name}");
|
2025-04-21 13:21:02 -06:00
|
|
|
MountExt::eject_with_operation(
|
|
|
|
|
&mount,
|
|
|
|
|
gio::MountUnmountFlags::NONE,
|
|
|
|
|
gio::MountOperation::NONE,
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
move |result| {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("eject {name}: result {result:?}");
|
2025-04-21 13:21:02 -06:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("unmount {name}");
|
2025-04-21 13:21:02 -06:00
|
|
|
MountExt::unmount_with_operation(
|
|
|
|
|
&mount,
|
|
|
|
|
gio::MountUnmountFlags::NONE,
|
|
|
|
|
gio::MountOperation::NONE,
|
|
|
|
|
gio::Cancellable::NONE,
|
|
|
|
|
move |result| {
|
2025-10-26 16:20:51 +10:00
|
|
|
log::info!("unmount {name}: result {result:?}");
|
2025-04-21 13:21:02 -06:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-04-24 13:59:55 -06:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-10-26 16:20:51 +10:00
|
|
|
main_loop.run();
|
2024-04-22 13:14:25 -06:00
|
|
|
});
|
|
|
|
|
Self {
|
|
|
|
|
command_tx,
|
2026-04-14 16:38:56 +02:00
|
|
|
event_rx: Arc::new(event_rx),
|
2024-04-22 09:54:00 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-22 13:14:25 -06:00
|
|
|
impl Mounter for Gvfs {
|
2024-10-04 16:28:30 -06:00
|
|
|
fn items(&self, sizes: IconSizes) -> Option<MounterItems> {
|
|
|
|
|
let (items_tx, mut items_rx) = mpsc::channel(1);
|
|
|
|
|
self.command_tx.send(Cmd::Items(sizes, items_tx)).unwrap();
|
|
|
|
|
items_rx.blocking_recv()
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 13:51:10 -06:00
|
|
|
fn mount(&self, item: MounterItem) -> Task<()> {
|
2024-04-22 13:14:25 -06:00
|
|
|
let command_tx = self.command_tx.clone();
|
2024-10-21 13:51:10 -06:00
|
|
|
Task::perform(
|
2024-04-22 13:14:25 -06:00
|
|
|
async move {
|
2025-07-12 19:42:04 -04:00
|
|
|
let (res_tx, res_rx) = tokio::sync::oneshot::channel();
|
|
|
|
|
|
|
|
|
|
command_tx.send(Cmd::Mount(item, res_tx)).unwrap();
|
|
|
|
|
res_rx.await
|
|
|
|
|
},
|
|
|
|
|
|x| {
|
|
|
|
|
if let Err(err) = x {
|
|
|
|
|
log::error!("{err:?}");
|
|
|
|
|
}
|
2024-04-22 13:14:25 -06:00
|
|
|
},
|
|
|
|
|
)
|
2024-04-22 09:54:00 -06:00
|
|
|
}
|
|
|
|
|
|
2024-10-21 13:51:10 -06:00
|
|
|
fn network_drive(&self, uri: String) -> Task<()> {
|
2024-09-12 15:54:54 -06:00
|
|
|
let command_tx = self.command_tx.clone();
|
2024-10-21 13:51:10 -06:00
|
|
|
Task::perform(
|
2024-09-12 15:54:54 -06:00
|
|
|
async move {
|
2025-07-12 19:42:04 -04:00
|
|
|
let (res_tx, res_rx) = tokio::sync::oneshot::channel();
|
|
|
|
|
|
|
|
|
|
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
|
|
|
|
|
res_rx.await
|
|
|
|
|
},
|
|
|
|
|
|x| {
|
|
|
|
|
if let Err(err) = x {
|
|
|
|
|
log::error!("{err:?}");
|
|
|
|
|
}
|
2024-09-12 15:54:54 -06:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 10:55:21 -04:00
|
|
|
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>> {
|
2024-09-13 15:13:37 -06:00
|
|
|
let (items_tx, mut items_rx) = mpsc::channel(1);
|
|
|
|
|
self.command_tx
|
2025-07-15 10:55:21 -04:00
|
|
|
.send(Cmd::NetworkScan(uri.to_string(), sizes, items_tx))
|
2024-09-13 15:13:37 -06:00
|
|
|
.unwrap();
|
|
|
|
|
items_rx.blocking_recv()
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-07 09:53:22 +10:00
|
|
|
fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)> {
|
2025-11-22 12:29:02 +10:00
|
|
|
let (result_tx, mut result_rx) = mpsc::channel(1);
|
|
|
|
|
self.command_tx
|
|
|
|
|
.send(Cmd::DirInfo(uri.to_string(), result_tx))
|
|
|
|
|
.unwrap();
|
|
|
|
|
result_rx.blocking_recv().and_then(|res| res.ok())
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 13:51:10 -06:00
|
|
|
fn unmount(&self, item: MounterItem) -> Task<()> {
|
2024-04-24 13:59:55 -06:00
|
|
|
let command_tx = self.command_tx.clone();
|
2025-10-28 13:10:40 +10:00
|
|
|
Task::future(async move {
|
|
|
|
|
command_tx.send(Cmd::Unmount(item)).unwrap();
|
|
|
|
|
})
|
2024-04-24 13:59:55 -06:00
|
|
|
}
|
|
|
|
|
|
2024-10-21 13:51:10 -06:00
|
|
|
fn subscription(&self) -> Subscription<MounterMessage> {
|
2024-04-22 13:14:25 -06:00
|
|
|
let command_tx = self.command_tx.clone();
|
|
|
|
|
let event_rx = self.event_rx.clone();
|
2026-03-13 16:04:17 -04:00
|
|
|
struct Wrapper {
|
|
|
|
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
2026-04-14 16:38:56 +02:00
|
|
|
event_rx: Arc<crate::channel::Receiver<Event>>,
|
2026-03-13 16:04:17 -04:00
|
|
|
}
|
|
|
|
|
impl Hash for Wrapper {
|
|
|
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
|
TypeId::of::<Self>().hash(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Subscription::run_with(
|
|
|
|
|
Wrapper {
|
|
|
|
|
command_tx,
|
|
|
|
|
event_rx,
|
|
|
|
|
},
|
|
|
|
|
|Wrapper {
|
|
|
|
|
command_tx,
|
|
|
|
|
event_rx,
|
|
|
|
|
}| {
|
|
|
|
|
let command_tx = command_tx.clone();
|
|
|
|
|
let event_rx = event_rx.clone();
|
|
|
|
|
stream::channel(
|
|
|
|
|
1,
|
|
|
|
|
move |mut output: cosmic::iced::futures::channel::mpsc::Sender<
|
|
|
|
|
MounterMessage,
|
|
|
|
|
>| async move {
|
|
|
|
|
command_tx.send(Cmd::Rescan).unwrap();
|
2026-04-14 16:38:56 +02:00
|
|
|
while let Some(event) = event_rx.recv().await {
|
2026-03-13 16:04:17 -04:00
|
|
|
match event {
|
|
|
|
|
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
|
|
|
|
|
Event::Items(items) => {
|
|
|
|
|
output.send(MounterMessage::Items(items)).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
Event::MountResult(item, res) => output
|
|
|
|
|
.send(MounterMessage::MountResult(item, res))
|
|
|
|
|
.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(),
|
|
|
|
|
}
|
2024-10-21 13:51:10 -06:00
|
|
|
}
|
2026-03-13 16:04:17 -04:00
|
|
|
pending().await
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
},
|
2024-10-21 13:51:10 -06:00
|
|
|
)
|
2024-04-22 09:54:00 -06:00
|
|
|
}
|
|
|
|
|
}
|