Refresh gvfs volumes as needed
This commit is contained in:
parent
02b6cda872
commit
2b1abc7c23
4 changed files with 342 additions and 88 deletions
|
|
@ -4,22 +4,36 @@ fn main() {
|
||||||
let monitor = gio::VolumeMonitor::get();
|
let monitor = gio::VolumeMonitor::get();
|
||||||
for drive in monitor.connected_drives() {
|
for drive in monitor.connected_drives() {
|
||||||
println!("Drive: {}", drive.name());
|
println!("Drive: {}", drive.name());
|
||||||
|
for id in drive.enumerate_identifiers() {
|
||||||
|
println!(" ID: {}={:?}", id, drive.identifier(&id));
|
||||||
|
}
|
||||||
for volume in drive.volumes() {
|
for volume in drive.volumes() {
|
||||||
println!(" Volume: {}", volume.name());
|
println!(" Volume: {}", volume.name());
|
||||||
|
println!(" UUID: {:?}", volume.uuid());
|
||||||
|
for id in volume.enumerate_identifiers() {
|
||||||
|
println!(" ID: {}={:?}", id, volume.identifier(&id));
|
||||||
|
}
|
||||||
if let Some(mount) = volume.get_mount() {
|
if let Some(mount) = volume.get_mount() {
|
||||||
println!(" Mount: {}", mount.name());
|
println!(" Mount: {}", mount.name());
|
||||||
|
println!(" UUID: {:?}", mount.uuid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for mount in monitor.mounts() {
|
for mount in monitor.mounts() {
|
||||||
println!("Mount: {}", mount.name());
|
println!("Mount: {}", mount.name());
|
||||||
|
println!(" UUID: {:?}", mount.uuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
for volume in monitor.volumes() {
|
for volume in monitor.volumes() {
|
||||||
println!("Volume: {}", volume.name());
|
println!("Volume: {}", volume.name());
|
||||||
|
println!(" UUID: {:?}", volume.uuid());
|
||||||
|
for id in volume.enumerate_identifiers() {
|
||||||
|
println!(" ID: {}={:?}", id, volume.identifier(&id));
|
||||||
|
}
|
||||||
if let Some(mount) = volume.get_mount() {
|
if let Some(mount) = volume.get_mount() {
|
||||||
println!(" Mount: {}", mount.name());
|
println!(" Mount: {}", mount.name());
|
||||||
|
println!(" UUID: {:?}", mount.uuid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
113
src/app.rs
113
src/app.rs
|
|
@ -29,7 +29,6 @@ use notify_debouncer_full::{
|
||||||
notify::{self, RecommendedWatcher, Watcher},
|
notify::{self, RecommendedWatcher, Watcher},
|
||||||
DebouncedEvent, Debouncer, FileIdMap,
|
DebouncedEvent, Debouncer, FileIdMap,
|
||||||
};
|
};
|
||||||
use std::time::Instant;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
collections::{BTreeMap, HashMap, HashSet, VecDeque},
|
||||||
|
|
@ -38,7 +37,7 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process,
|
process,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time,
|
time::{self, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tab::HOVER_DURATION;
|
use crate::tab::HOVER_DURATION;
|
||||||
|
|
@ -48,7 +47,7 @@ use crate::{
|
||||||
fl, home_dir,
|
fl, home_dir,
|
||||||
key_bind::key_binds,
|
key_bind::key_binds,
|
||||||
menu, mime_app,
|
menu, mime_app,
|
||||||
mounter::{mounters, Mounters},
|
mounter::{mounters, MounterItem, MounterItems, MounterKey, Mounters},
|
||||||
operation::Operation,
|
operation::Operation,
|
||||||
spawn_detached::spawn_detached,
|
spawn_detached::spawn_detached,
|
||||||
tab::{self, HeadingOptions, ItemMetadata, Location, Tab},
|
tab::{self, HeadingOptions, ItemMetadata, Location, Tab},
|
||||||
|
|
@ -160,6 +159,7 @@ pub enum Message {
|
||||||
LaunchUrl(String),
|
LaunchUrl(String),
|
||||||
Modifiers(Modifiers),
|
Modifiers(Modifiers),
|
||||||
MoveToTrash(Option<Entity>),
|
MoveToTrash(Option<Entity>),
|
||||||
|
MounterItems(MounterKey, MounterItems),
|
||||||
NewItem(Option<Entity>, bool),
|
NewItem(Option<Entity>, bool),
|
||||||
NotifyEvents(Vec<DebouncedEvent>),
|
NotifyEvents(Vec<DebouncedEvent>),
|
||||||
NotifyWatcher(WatcherWrapper),
|
NotifyWatcher(WatcherWrapper),
|
||||||
|
|
@ -232,6 +232,8 @@ pub enum DialogPage {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MounterData(MounterKey, MounterItem);
|
||||||
|
|
||||||
pub struct WatcherWrapper {
|
pub struct WatcherWrapper {
|
||||||
watcher_opt: Option<Debouncer<RecommendedWatcher, FileIdMap>>,
|
watcher_opt: Option<Debouncer<RecommendedWatcher, FileIdMap>>,
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +272,7 @@ pub struct App {
|
||||||
key_binds: HashMap<KeyBind, Action>,
|
key_binds: HashMap<KeyBind, Action>,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
mounters: Mounters,
|
mounters: Mounters,
|
||||||
|
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||||
pending_operation_id: u64,
|
pending_operation_id: u64,
|
||||||
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
||||||
complete_operations: BTreeMap<u64, Operation>,
|
complete_operations: BTreeMap<u64, Operation>,
|
||||||
|
|
@ -729,34 +732,6 @@ impl Application for App {
|
||||||
.data(Location::Trash)
|
.data(Location::Trash)
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: dynamic mount list
|
|
||||||
let mounters = mounters();
|
|
||||||
for (mounter_name, mounter) in mounters.iter() {
|
|
||||||
println!("Mounter {}", mounter_name);
|
|
||||||
match mounter.items() {
|
|
||||||
Ok(items) => {
|
|
||||||
for item in items {
|
|
||||||
let name = item.name();
|
|
||||||
println!(" - {}", name);
|
|
||||||
let icon = item.icon(16);
|
|
||||||
let dir_opt = item.path();
|
|
||||||
nav_model = nav_model.insert(move |mut b| {
|
|
||||||
b = b
|
|
||||||
.text(name.clone())
|
|
||||||
.icon(widget::icon::icon(icon.clone()).size(16));
|
|
||||||
match &dir_opt {
|
|
||||||
Some(dir) => b.data(Location::Path(dir.clone())),
|
|
||||||
None => b,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to get items: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
core,
|
core,
|
||||||
nav_model: nav_model.build(),
|
nav_model: nav_model.build(),
|
||||||
|
|
@ -771,7 +746,8 @@ impl Application for App {
|
||||||
dialog_text_input: widget::Id::unique(),
|
dialog_text_input: widget::Id::unique(),
|
||||||
key_binds: key_binds(),
|
key_binds: key_binds(),
|
||||||
modifiers: Modifiers::empty(),
|
modifiers: Modifiers::empty(),
|
||||||
mounters,
|
mounters: mounters(),
|
||||||
|
mounter_items: HashMap::new(),
|
||||||
pending_operation_id: 0,
|
pending_operation_id: 0,
|
||||||
pending_operations: BTreeMap::new(),
|
pending_operations: BTreeMap::new(),
|
||||||
complete_operations: BTreeMap::new(),
|
complete_operations: BTreeMap::new(),
|
||||||
|
|
@ -816,13 +792,17 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_nav_select(&mut self, entity: Entity) -> Command<Self::Message> {
|
fn on_nav_select(&mut self, entity: Entity) -> Command<Self::Message> {
|
||||||
let location_opt = self.nav_model.data::<Location>(entity).clone();
|
if let Some(location) = self.nav_model.data::<Location>(entity) {
|
||||||
|
|
||||||
if let Some(location) = location_opt {
|
|
||||||
let message = Message::TabMessage(None, tab::Message::Location(location.clone()));
|
let message = Message::TabMessage(None, tab::Message::Location(location.clone()));
|
||||||
return self.update(message);
|
return self.update(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(data) = self.nav_model.data::<MounterData>(entity).clone() {
|
||||||
|
if let Some(mounter) = self.mounters.get(&data.0) {
|
||||||
|
return mounter.mount(data.1.clone()).map(|_| message::none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -979,6 +959,51 @@ impl Application for App {
|
||||||
self.operation(Operation::Delete { paths });
|
self.operation(Operation::Delete { paths });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::MounterItems(mounter_key, mounter_items) => {
|
||||||
|
// Insert new items
|
||||||
|
self.mounter_items.insert(mounter_key, mounter_items);
|
||||||
|
|
||||||
|
// Remove any items with mounter data from nav model
|
||||||
|
let entities: Vec<Entity> = self.nav_model.iter().collect();
|
||||||
|
for entity in entities {
|
||||||
|
if self.nav_model.data::<MounterData>(entity).is_some() {
|
||||||
|
self.nav_model.remove(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all mounter items
|
||||||
|
let mut nav_items = Vec::new();
|
||||||
|
for (key, items) in self.mounter_items.iter() {
|
||||||
|
for item in items.iter() {
|
||||||
|
nav_items.push((*key, item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by name lexically
|
||||||
|
nav_items
|
||||||
|
.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.1.name(), &b.1.name()));
|
||||||
|
// Add items to nav model
|
||||||
|
for (key, item) in nav_items {
|
||||||
|
let mut entity = self
|
||||||
|
.nav_model
|
||||||
|
.insert()
|
||||||
|
.text(format!(
|
||||||
|
"{} ({})",
|
||||||
|
item.name(),
|
||||||
|
if item.is_mounted() {
|
||||||
|
"mounted"
|
||||||
|
} else {
|
||||||
|
"not mounted"
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.data(MounterData(key, item.clone()));
|
||||||
|
if let Some(path) = item.path() {
|
||||||
|
entity = entity.data(Location::Path(path.clone()));
|
||||||
|
}
|
||||||
|
if let Some(icon) = item.icon() {
|
||||||
|
entity = entity.icon(widget::icon::icon(icon).size(16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::NewItem(entity_opt, dir) => {
|
Message::NewItem(entity_opt, dir) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
|
|
@ -1907,10 +1932,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: how to properly kill this task?
|
std::future::pending().await
|
||||||
loop {
|
|
||||||
tokio::time::sleep(time::Duration::new(1, 0)).await;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
subscription::channel(
|
subscription::channel(
|
||||||
|
|
@ -1981,6 +2003,15 @@ impl Application for App {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
for (key, mounter) in self.mounters.iter() {
|
||||||
|
let key = *key;
|
||||||
|
subscriptions.push(
|
||||||
|
mounter
|
||||||
|
.subscription()
|
||||||
|
.map(move |items| Message::MounterItems(key, items)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (id, (pending_operation, _)) in self.pending_operations.iter() {
|
for (id, (pending_operation, _)) in self.pending_operations.iter() {
|
||||||
//TODO: use recipe?
|
//TODO: use recipe?
|
||||||
let id = *id;
|
let id = *id;
|
||||||
|
|
@ -2000,9 +2031,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
std::future::pending().await
|
||||||
tokio::time::sleep(time::Duration::new(1, 0)).await;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,233 @@
|
||||||
use cosmic::widget;
|
use cosmic::{
|
||||||
use gio::prelude::*;
|
iced::{futures::SinkExt, subscription},
|
||||||
use gio::{Mount, ThemedIcon, Volume, VolumeMonitor};
|
widget, Command,
|
||||||
use std::{error::Error, path::PathBuf};
|
};
|
||||||
|
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, 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 {
|
pub struct Gvfs {
|
||||||
monitor: VolumeMonitor,
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||||
|
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gvfs {
|
impl Gvfs {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let monitor = VolumeMonitor::get();
|
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
||||||
Self { monitor }
|
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 {
|
impl Mounter for Gvfs {
|
||||||
fn items(&self) -> Result<MounterItems, Box<dyn Error>> {
|
fn mount(&self, item: MounterItem) -> Command<()> {
|
||||||
let mut items = MounterItems::new();
|
let command_tx = self.command_tx.clone();
|
||||||
for mount in self.monitor.mounts() {
|
Command::perform(
|
||||||
items.push(Box::new(mount));
|
async move {
|
||||||
}
|
command_tx.send(Cmd::Mount(item)).unwrap();
|
||||||
Ok(items)
|
()
|
||||||
}
|
},
|
||||||
}
|
|x| x,
|
||||||
|
)
|
||||||
impl MounterItem for Mount {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
MountExt::name(self).to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self, size: u16) -> widget::icon::Handle {
|
fn subscription(&self) -> subscription::Subscription<MounterItems> {
|
||||||
let icon = MountExt::symbolic_icon(self);
|
let command_tx = self.command_tx.clone();
|
||||||
if let Some(themed_icon) = icon.downcast_ref::<ThemedIcon>() {
|
let event_rx = self.event_rx.clone();
|
||||||
for name in themed_icon.names() {
|
subscription::channel(TypeId::of::<Self>(), 1, |mut output| async move {
|
||||||
let named = widget::icon::from_name(name.as_str()).size(size);
|
command_tx.send(Cmd::Rescan).unwrap();
|
||||||
if let Some(path) = named.path() {
|
while let Some(event) = event_rx.lock().await.recv().await {
|
||||||
return widget::icon::from_path(path);
|
match event {
|
||||||
|
Event::Changed => {
|
||||||
|
command_tx.send(Cmd::Rescan).unwrap();
|
||||||
|
}
|
||||||
|
Event::Items(items) => output.send(items).await.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pending().await
|
||||||
|
})
|
||||||
//TODO: handle more gio icon types
|
|
||||||
widget::icon::from_name("folder-symbolic")
|
|
||||||
.size(size)
|
|
||||||
.handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> Option<PathBuf> {
|
|
||||||
MountExt::root(self).path()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,55 @@
|
||||||
use cosmic::widget;
|
use cosmic::{iced::subscription, widget, Command};
|
||||||
use std::error::Error;
|
|
||||||
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
#[cfg(feature = "gvfs")]
|
||||||
mod gvfs;
|
mod gvfs;
|
||||||
|
|
||||||
pub trait MounterItem {
|
#[derive(Clone, Debug)]
|
||||||
fn name(&self) -> String;
|
pub enum MounterItem {
|
||||||
fn icon(&self, size: u16) -> widget::icon::Handle;
|
#[cfg(feature = "gvfs")]
|
||||||
fn path(&self) -> Option<PathBuf>;
|
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 {
|
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 MounterMap = BTreeMap<MounterKey, Box<dyn Mounter>>;
|
||||||
pub type Mounters = Arc<MounterMap>;
|
pub type Mounters = Arc<MounterMap>;
|
||||||
|
|
||||||
|
|
@ -26,7 +58,7 @@ pub fn mounters() -> Mounters {
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
#[cfg(feature = "gvfs")]
|
||||||
{
|
{
|
||||||
mounters.insert("gvfs", Box::new(gvfs::Gvfs::new()));
|
mounters.insert(MounterKey("gvfs"), Box::new(gvfs::Gvfs::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Mounters::new(mounters)
|
Mounters::new(mounters)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue