Refresh gvfs volumes as needed
This commit is contained in:
parent
02b6cda872
commit
2b1abc7c23
4 changed files with 342 additions and 88 deletions
|
|
@ -1,54 +1,233 @@
|
|||
use cosmic::widget;
|
||||
use gio::prelude::*;
|
||||
use gio::{Mount, ThemedIcon, Volume, VolumeMonitor};
|
||||
use std::{error::Error, path::PathBuf};
|
||||
use cosmic::{
|
||||
iced::{futures::SinkExt, subscription},
|
||||
widget, Command,
|
||||
};
|
||||
use gio::{glib, prelude::*};
|
||||
use std::{any::TypeId, future::pending, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
|
||||
use super::{Mounter, MounterItem, MounterItems};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
enum Cmd {
|
||||
Rescan,
|
||||
Mount(MounterItem),
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Changed,
|
||||
Items(MounterItems),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ItemKind {
|
||||
Mount,
|
||||
Volume,
|
||||
}
|
||||
|
||||
//TODO: better method of matching items
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Item {
|
||||
kind: ItemKind,
|
||||
index: usize,
|
||||
name: String,
|
||||
is_mounted: bool,
|
||||
icon_opt: Option<PathBuf>,
|
||||
path_opt: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
self.is_mounted
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> Option<widget::icon::Handle> {
|
||||
self.icon_opt
|
||||
.as_ref()
|
||||
.map(|icon| widget::icon::from_path(icon.clone()))
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<PathBuf> {
|
||||
self.path_opt.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gvfs {
|
||||
monitor: VolumeMonitor,
|
||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
||||
}
|
||||
|
||||
impl Gvfs {
|
||||
pub fn new() -> Self {
|
||||
let monitor = VolumeMonitor::get();
|
||||
Self { monitor }
|
||||
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
||||
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
std::thread::spawn(move || {
|
||||
let main_loop = glib::MainLoop::new(None, false);
|
||||
main_loop.context().spawn_local(async move {
|
||||
let monitor = gio::VolumeMonitor::get();
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_changed(move |_monitor, mount| {
|
||||
eprintln!("mount changed {}", MountExt::name(mount));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_added(move |_monitor, mount| {
|
||||
eprintln!("mount added {}", MountExt::name(mount));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_removed(move |_monitor, mount| {
|
||||
eprintln!("mount removed {}", MountExt::name(mount));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_changed(move |_monitor, volume| {
|
||||
eprintln!("volume changed {}", VolumeExt::name(volume));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_added(move |_monitor, volume| {
|
||||
eprintln!("volume added {}", VolumeExt::name(volume));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_removed(move |_monitor, volume| {
|
||||
eprintln!("volume removed {}", VolumeExt::name(volume));
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
while let Some(command) = command_rx.recv().await {
|
||||
match command {
|
||||
Cmd::Rescan => {
|
||||
let mut items = MounterItems::new();
|
||||
for (i, mount) in monitor.mounts().into_iter().enumerate() {
|
||||
items.push(MounterItem::Gvfs(Item {
|
||||
kind: ItemKind::Mount,
|
||||
index: i,
|
||||
name: MountExt::name(&mount).to_string(),
|
||||
is_mounted: true,
|
||||
icon_opt: gio_icon_to_path(
|
||||
&MountExt::symbolic_icon(&mount),
|
||||
16,
|
||||
),
|
||||
path_opt: MountExt::root(&mount).path(),
|
||||
}));
|
||||
}
|
||||
for (i, volume) in monitor.volumes().into_iter().enumerate() {
|
||||
if volume.get_mount().is_some() {
|
||||
// Volumes with mounts are already listed by mount
|
||||
continue;
|
||||
}
|
||||
items.push(MounterItem::Gvfs(Item {
|
||||
kind: ItemKind::Volume,
|
||||
index: i,
|
||||
name: VolumeExt::name(&volume).to_string(),
|
||||
is_mounted: false,
|
||||
icon_opt: gio_icon_to_path(
|
||||
&VolumeExt::symbolic_icon(&volume),
|
||||
16,
|
||||
),
|
||||
path_opt: None,
|
||||
}));
|
||||
}
|
||||
event_tx.send(Event::Items(items)).unwrap();
|
||||
}
|
||||
Cmd::Mount(mounter_item) => {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
let MounterItem::Gvfs(item) = mounter_item else { continue };
|
||||
let ItemKind::Volume = item.kind else { continue };
|
||||
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;
|
||||
}
|
||||
|
||||
log::info!("mount {}", name);
|
||||
VolumeExt::mount(
|
||||
&volume,
|
||||
gio::MountMountFlags::NONE,
|
||||
//TODO: gio::MountOperation needed for network shares with auth
|
||||
gio::MountOperation::NONE,
|
||||
gio::Cancellable::NONE,
|
||||
move |result| {
|
||||
log::info!("mount {}: result {:?}", name, result);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
main_loop.run()
|
||||
});
|
||||
Self {
|
||||
command_tx,
|
||||
event_rx: Arc::new(Mutex::new(event_rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mounter for Gvfs {
|
||||
fn items(&self) -> Result<MounterItems, Box<dyn Error>> {
|
||||
let mut items = MounterItems::new();
|
||||
for mount in self.monitor.mounts() {
|
||||
items.push(Box::new(mount));
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
impl MounterItem for Mount {
|
||||
fn name(&self) -> String {
|
||||
MountExt::name(self).to_string()
|
||||
fn mount(&self, item: MounterItem) -> Command<()> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
command_tx.send(Cmd::Mount(item)).unwrap();
|
||||
()
|
||||
},
|
||||
|x| x,
|
||||
)
|
||||
}
|
||||
|
||||
fn icon(&self, size: u16) -> widget::icon::Handle {
|
||||
let icon = MountExt::symbolic_icon(self);
|
||||
if let Some(themed_icon) = icon.downcast_ref::<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 widget::icon::from_path(path);
|
||||
fn subscription(&self) -> subscription::Subscription<MounterItems> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: handle more gio icon types
|
||||
widget::icon::from_name("folder-symbolic")
|
||||
.size(size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<PathBuf> {
|
||||
MountExt::root(self).path()
|
||||
pending().await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,55 @@
|
|||
use cosmic::widget;
|
||||
use std::error::Error;
|
||||
use cosmic::{iced::subscription, widget, Command};
|
||||
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||
|
||||
#[cfg(feature = "gvfs")]
|
||||
mod gvfs;
|
||||
|
||||
pub trait MounterItem {
|
||||
fn name(&self) -> String;
|
||||
fn icon(&self, size: u16) -> widget::icon::Handle;
|
||||
fn path(&self) -> Option<PathBuf>;
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MounterItem {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Gvfs(gvfs::Item),
|
||||
}
|
||||
|
||||
pub type MounterItems = Vec<Box<dyn MounterItem>>;
|
||||
impl MounterItem {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Self::Gvfs(item) => item.name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Self::Gvfs(item) => item.is_mounted(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> Option<widget::icon::Handle> {
|
||||
match self {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Self::Gvfs(item) => item.icon(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<PathBuf> {
|
||||
match self {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Self::Gvfs(item) => item.path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type MounterItems = Vec<MounterItem>;
|
||||
|
||||
pub trait Mounter {
|
||||
fn items(&self) -> Result<MounterItems, Box<dyn Error>>;
|
||||
//TODO: send result
|
||||
fn mount(&self, item: MounterItem) -> Command<()>;
|
||||
fn subscription(&self) -> subscription::Subscription<MounterItems>;
|
||||
}
|
||||
|
||||
pub type MounterKey = &'static str;
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct MounterKey(pub &'static str);
|
||||
pub type MounterMap = BTreeMap<MounterKey, Box<dyn Mounter>>;
|
||||
pub type Mounters = Arc<MounterMap>;
|
||||
|
||||
|
|
@ -26,7 +58,7 @@ pub fn mounters() -> Mounters {
|
|||
|
||||
#[cfg(feature = "gvfs")]
|
||||
{
|
||||
mounters.insert("gvfs", Box::new(gvfs::Gvfs::new()));
|
||||
mounters.insert(MounterKey("gvfs"), Box::new(gvfs::Gvfs::new()));
|
||||
}
|
||||
|
||||
Mounters::new(mounters)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue