WIP: support for network browsing
This commit is contained in:
parent
cd71c895f9
commit
c3d6498042
10 changed files with 251 additions and 85 deletions
31
examples/gio-list.rs
Normal file
31
examples/gio-list.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use gio::prelude::*;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let uri = env::args().nth(1).expect("no uri provided");
|
||||||
|
let file = gio::File::for_uri(&uri);
|
||||||
|
for entry_res in file
|
||||||
|
.enumerate_children("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE)
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
let entry = entry_res.unwrap();
|
||||||
|
println!("{:?}", entry.display_name());
|
||||||
|
for attribute in entry.list_attributes(None) {
|
||||||
|
println!(
|
||||||
|
" {:?}: {:?}",
|
||||||
|
attribute,
|
||||||
|
entry.attribute_as_string(&attribute)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: what is the best way to resolve shortcuts?
|
||||||
|
let child = if let Some(target_uri) =
|
||||||
|
entry.attribute_string(gio::FILE_ATTRIBUTE_STANDARD_TARGET_URI)
|
||||||
|
{
|
||||||
|
gio::File::for_uri(&target_uri)
|
||||||
|
} else {
|
||||||
|
file.child(entry.name())
|
||||||
|
};
|
||||||
|
println!("{:?}", child.uri());
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/app.rs
64
src/app.rs
|
|
@ -521,11 +521,14 @@ impl App {
|
||||||
location: Location,
|
location: Location,
|
||||||
selection_path: Option<PathBuf>,
|
selection_path: Option<PathBuf>,
|
||||||
) -> Command<Message> {
|
) -> Command<Message> {
|
||||||
|
let mounters = self.mounters.clone();
|
||||||
let icon_sizes = self.config.tab.icon_sizes;
|
let icon_sizes = self.config.tab.icon_sizes;
|
||||||
Command::perform(
|
Command::perform(
|
||||||
async move {
|
async move {
|
||||||
let location2 = location.clone();
|
let location2 = location.clone();
|
||||||
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
|
match tokio::task::spawn_blocking(move || location2.scan(mounters, icon_sizes))
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
message::app(Message::TabRescan(entity, location, items, selection_path))
|
message::app(Message::TabRescan(entity, location, items, selection_path))
|
||||||
}
|
}
|
||||||
|
|
@ -672,7 +675,10 @@ impl App {
|
||||||
.size(16)
|
.size(16)
|
||||||
.handle(),
|
.handle(),
|
||||||
))
|
))
|
||||||
.data(Location::Networks)
|
.data(Location::Network(
|
||||||
|
"network:///".to_string(),
|
||||||
|
fl!("networks"),
|
||||||
|
))
|
||||||
.divider_above()
|
.divider_above()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -941,49 +947,35 @@ impl App {
|
||||||
fn properties(&self, entity: Option<ContextItem>) -> Element<Message> {
|
fn properties(&self, entity: Option<ContextItem>) -> Element<Message> {
|
||||||
match entity {
|
match entity {
|
||||||
None => self.tab_properties(self.tab_model.active()),
|
None => self.tab_properties(self.tab_model.active()),
|
||||||
|
|
||||||
Some(ContextItem::TabBar(entity)) => self.tab_properties(entity),
|
Some(ContextItem::TabBar(entity)) => self.tab_properties(entity),
|
||||||
|
|
||||||
Some(ContextItem::NavBar(item)) => {
|
Some(ContextItem::NavBar(item)) => {
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::with_capacity(1);
|
||||||
|
|
||||||
if let Some(location) = self.nav_model.data::<Location>(item) {
|
if let Some(location) = self.nav_model.data::<Location>(item) {
|
||||||
if let Location::Path(path) = location {
|
if let Location::Path(path) = location {
|
||||||
let parent = path.parent().unwrap_or(path);
|
//TODO: this should be done once, not when generating the view!
|
||||||
|
if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) {
|
||||||
for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) {
|
children.push(item.property_view(IconSizes::default()));
|
||||||
if item.path_opt() == Some(path) {
|
|
||||||
children.push(item.property_view(IconSizes::default()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
widget::settings::view_column(children).into()
|
widget::settings::view_column(children).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ContextItem::BreadCrumbs(index)) => {
|
Some(ContextItem::BreadCrumbs(index)) => {
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::with_capacity(1);
|
||||||
|
|
||||||
if let Some(tab) = self.tab_model.active_data::<Tab>() {
|
if let Some(tab) = self.tab_model.active_data::<Tab>() {
|
||||||
let path = match tab.location {
|
let path_opt = tab
|
||||||
Location::Path(ref path) => Some(path),
|
.location
|
||||||
Location::Search(ref path, _) => Some(path),
|
.path_opt()
|
||||||
_ => None,
|
.and_then(|path| path.ancestors().nth(index))
|
||||||
}
|
.map(|path| path.to_path_buf());
|
||||||
.and_then(|path| path.ancestors().nth(index))
|
if let Some(ref path) = path_opt {
|
||||||
.map(|path| path.to_path_buf());
|
//TODO: this should be done once, not when generating the view!
|
||||||
if let Some(ref path) = path {
|
if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) {
|
||||||
let parent = path.parent().unwrap_or(path);
|
children.push(item.property_view(IconSizes::default()));
|
||||||
|
|
||||||
for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) {
|
|
||||||
if item.path_opt() == Some(path) {
|
|
||||||
children.push(item.property_view(IconSizes::default()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
widget::settings::view_column(children).into()
|
widget::settings::view_column(children).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2396,11 +2388,14 @@ impl Application for App {
|
||||||
self.toasts.remove(id);
|
self.toasts.remove(id);
|
||||||
|
|
||||||
let mut paths = Vec::with_capacity(recently_trashed.len());
|
let mut paths = Vec::with_capacity(recently_trashed.len());
|
||||||
|
let mounters = self.mounters.clone();
|
||||||
let icon_sizes = self.config.tab.icon_sizes;
|
let icon_sizes = self.config.tab.icon_sizes;
|
||||||
|
|
||||||
return cosmic::command::future(async move {
|
return cosmic::command::future(async move {
|
||||||
match tokio::task::spawn_blocking(move || Location::Trash.scan(icon_sizes))
|
match tokio::task::spawn_blocking(move || {
|
||||||
.await
|
Location::Trash.scan(mounters, icon_sizes)
|
||||||
|
})
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
for path in &*recently_trashed {
|
for path in &*recently_trashed {
|
||||||
|
|
@ -3549,6 +3544,7 @@ pub(crate) mod test_utils {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{IconSizes, TabConfig},
|
config::{IconSizes, TabConfig},
|
||||||
|
mounter::MounterMap,
|
||||||
tab::Item,
|
tab::Item,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3711,7 +3707,7 @@ pub(crate) mod test_utils {
|
||||||
|
|
||||||
// New tab with items
|
// New tab with items
|
||||||
let location = Location::Path(path.to_owned());
|
let location = Location::Path(path.to_owned());
|
||||||
let items = location.scan(IconSizes::default());
|
let items = location.scan(Mounters::new(MounterMap::new()), IconSizes::default());
|
||||||
let mut tab = Tab::new(location, TabConfig::default());
|
let mut tab = Tab::new(location, TabConfig::default());
|
||||||
tab.set_items(items);
|
tab.set_items(items);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -376,10 +376,12 @@ struct App {
|
||||||
impl App {
|
impl App {
|
||||||
fn rescan_tab(&self) -> Command<Message> {
|
fn rescan_tab(&self) -> Command<Message> {
|
||||||
let location = self.tab.location.clone();
|
let location = self.tab.location.clone();
|
||||||
|
let mounters = self.mounters.clone();
|
||||||
let icon_sizes = self.tab.config.icon_sizes;
|
let icon_sizes = self.tab.config.icon_sizes;
|
||||||
Command::perform(
|
Command::perform(
|
||||||
async move {
|
async move {
|
||||||
match tokio::task::spawn_blocking(move || location.scan(icon_sizes)).await {
|
match tokio::task::spawn_blocking(move || location.scan(mounters, icon_sizes)).await
|
||||||
|
{
|
||||||
Ok(items) => message::app(Message::TabRescan(items)),
|
Ok(items) => message::app(Message::TabRescan(items)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to rescan: {}", err);
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
|
@ -895,7 +897,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DialogPage::Replace { filename } => {
|
DialogPage::Replace { .. } => {
|
||||||
return self.update(Message::Save(true));
|
return self.update(Message::Save(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ mod spawn_detached;
|
||||||
use tab::Location;
|
use tab::Location;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
||||||
|
pub(crate) fn err_str<T: ToString>(err: T) -> String {
|
||||||
|
err.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn home_dir() -> PathBuf {
|
pub fn home_dir() -> PathBuf {
|
||||||
match dirs::home_dir() {
|
match dirs::home_dir() {
|
||||||
Some(home) => home,
|
Some(home) => home,
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ pub fn context_menu<'a>(
|
||||||
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
|
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, Location::Networks) => {
|
(_, Location::Network(_, _)) => {
|
||||||
//TODO: networks context menu?
|
//TODO: networks context menu?
|
||||||
}
|
}
|
||||||
(_, Location::Trash) => {
|
(_, Location::Trash) => {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,15 @@ use cosmic::{
|
||||||
widget, Command,
|
widget, Command,
|
||||||
};
|
};
|
||||||
use gio::{glib, prelude::*};
|
use gio::{glib, prelude::*};
|
||||||
use std::{any::TypeId, future::pending, path::PathBuf, sync::Arc};
|
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
|
|
||||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||||
|
use crate::{
|
||||||
|
config::IconSizes,
|
||||||
|
err_str,
|
||||||
|
tab::{self, ItemMetadata, ItemThumbnail, Location},
|
||||||
|
};
|
||||||
|
|
||||||
fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
||||||
if let Some(themed_icon) = icon.downcast_ref::<gio::ThemedIcon>() {
|
if let Some(themed_icon) = icon.downcast_ref::<gio::ThemedIcon>() {
|
||||||
|
|
@ -21,10 +26,97 @@ fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
let file = gio::File::for_uri(&uri);
|
||||||
|
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)?;
|
||||||
|
println!("{:?}", info.display_name());
|
||||||
|
for attribute in info.list_attributes(None) {
|
||||||
|
println!(
|
||||||
|
" {:?}: {:?}: {:?}",
|
||||||
|
attribute,
|
||||||
|
info.attribute_type(&attribute),
|
||||||
|
info.attribute_as_string(&attribute)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = info.name().to_string_lossy().to_string();
|
||||||
|
let display_name = info.display_name().to_string();
|
||||||
|
|
||||||
|
//TODO: what is the best way to resolve shortcuts?
|
||||||
|
let location = Location::Network(
|
||||||
|
if let Some(target_uri) = info.attribute_string(gio::FILE_ATTRIBUTE_STANDARD_TARGET_URI)
|
||||||
|
{
|
||||||
|
target_uri.to_string()
|
||||||
|
} else {
|
||||||
|
file.child(info.name()).uri().to_string()
|
||||||
|
},
|
||||||
|
display_name.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
//TODO: support dir or file
|
||||||
|
let metadata = ItemMetadata::SimpleDir { entries: 0 };
|
||||||
|
|
||||||
|
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))
|
||||||
|
.map(|path| widget::icon::from_path(path))
|
||||||
|
.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()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
items.push(tab::Item {
|
||||||
|
name,
|
||||||
|
display_name,
|
||||||
|
metadata,
|
||||||
|
hidden: false,
|
||||||
|
location_opt: Some(location),
|
||||||
|
mime,
|
||||||
|
icon_handle_grid,
|
||||||
|
icon_handle_list,
|
||||||
|
icon_handle_list_condensed,
|
||||||
|
open_with: Vec::new(),
|
||||||
|
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||||
|
button_id: widget::Id::unique(),
|
||||||
|
pos_opt: Cell::new(None),
|
||||||
|
rect_opt: Cell::new(None),
|
||||||
|
selected: false,
|
||||||
|
overlaps_drag_rect: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(items)
|
||||||
|
}
|
||||||
|
|
||||||
enum Cmd {
|
enum Cmd {
|
||||||
Rescan,
|
Rescan,
|
||||||
Mount(MounterItem),
|
Mount(MounterItem),
|
||||||
NetworkDrive(String),
|
NetworkDrive(String),
|
||||||
|
NetworkScan(
|
||||||
|
String,
|
||||||
|
IconSizes,
|
||||||
|
mpsc::Sender<Result<Vec<tab::Item>, String>>,
|
||||||
|
),
|
||||||
Unmount(MounterItem),
|
Unmount(MounterItem),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,6 +359,9 @@ impl Gvfs {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Cmd::NetworkScan(uri, sizes, items_tx) => {
|
||||||
|
items_tx.send(network_scan(&uri, sizes)).await.unwrap();
|
||||||
|
}
|
||||||
Cmd::Unmount(mounter_item) => {
|
Cmd::Unmount(mounter_item) => {
|
||||||
let MounterItem::Gvfs(item) = mounter_item else { continue };
|
let MounterItem::Gvfs(item) = mounter_item else { continue };
|
||||||
let ItemKind::Mount = item.kind else { continue };
|
let ItemKind::Mount = item.kind else { continue };
|
||||||
|
|
@ -330,6 +425,14 @@ impl Mounter for Gvfs {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>> {
|
||||||
|
let (items_tx, mut items_rx) = mpsc::channel(1);
|
||||||
|
self.command_tx
|
||||||
|
.send(Cmd::NetworkScan(uri.to_string(), sizes, items_tx))
|
||||||
|
.unwrap();
|
||||||
|
items_rx.blocking_recv()
|
||||||
|
}
|
||||||
|
|
||||||
fn unmount(&self, item: MounterItem) -> Command<()> {
|
fn unmount(&self, item: MounterItem) -> Command<()> {
|
||||||
let command_tx = self.command_tx.clone();
|
let command_tx = self.command_tx.clone();
|
||||||
Command::perform(
|
Command::perform(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use cosmic::{iced::subscription, widget, Command};
|
||||||
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::{config::IconSizes, tab};
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
#[cfg(feature = "gvfs")]
|
||||||
mod gvfs;
|
mod gvfs;
|
||||||
|
|
||||||
|
|
@ -86,10 +88,11 @@ pub enum MounterMessage {
|
||||||
NetworkResult(String, Result<bool, String>),
|
NetworkResult(String, Result<bool, String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Mounter {
|
pub trait Mounter: Send + Sync {
|
||||||
//TODO: send result
|
//TODO: send result
|
||||||
fn mount(&self, item: MounterItem) -> Command<()>;
|
fn mount(&self, item: MounterItem) -> Command<()>;
|
||||||
fn network_drive(&self, uri: String) -> Command<()>;
|
fn network_drive(&self, uri: String) -> Command<()>;
|
||||||
|
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
|
||||||
fn unmount(&self, item: MounterItem) -> Command<()>;
|
fn unmount(&self, item: MounterItem) -> Command<()>;
|
||||||
fn subscription(&self) -> subscription::Subscription<MounterMessage>;
|
fn subscription(&self) -> subscription::Subscription<MounterMessage>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,11 @@ use walkdir::WalkDir;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{ArchiveType, DialogPage, Message},
|
app::{ArchiveType, DialogPage, Message},
|
||||||
config::IconSizes,
|
config::IconSizes,
|
||||||
fl,
|
err_str, fl,
|
||||||
mime_icon::mime_for_path,
|
mime_icon::mime_for_path,
|
||||||
tab,
|
tab,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn err_str<T: ToString>(err: T) -> String {
|
|
||||||
err.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_replace(
|
fn handle_replace(
|
||||||
msg_tx: &Arc<Mutex<Sender<Message>>>,
|
msg_tx: &Arc<Mutex<Sender<Message>>>,
|
||||||
file_from: PathBuf,
|
file_from: PathBuf,
|
||||||
|
|
|
||||||
113
src/tab.rs
113
src/tab.rs
|
|
@ -63,6 +63,7 @@ use crate::{
|
||||||
menu,
|
menu,
|
||||||
mime_app::{mime_apps, MimeApp},
|
mime_app::{mime_apps, MimeApp},
|
||||||
mime_icon::{mime_for_path, mime_icon},
|
mime_icon::{mime_for_path, mime_icon},
|
||||||
|
mounter::Mounters,
|
||||||
mouse_area,
|
mouse_area,
|
||||||
};
|
};
|
||||||
use unix_permissions_ext::UNIXPermissionsExt;
|
use unix_permissions_ext::UNIXPermissionsExt;
|
||||||
|
|
@ -561,7 +562,7 @@ pub fn scan_search(tab_path: &PathBuf, term: &str, sizes: IconSizes) -> Vec<Item
|
||||||
items.par_sort_unstable_by(|a, b| {
|
items.par_sort_unstable_by(|a, b| {
|
||||||
let get_modified = |x: &Item| match &x.metadata {
|
let get_modified = |x: &Item| match &x.metadata {
|
||||||
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
||||||
ItemMetadata::Trash { .. } => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sort with latest modified first
|
// Sort with latest modified first
|
||||||
|
|
@ -750,9 +751,17 @@ pub fn scan_recents(sizes: IconSizes) -> Vec<Item> {
|
||||||
recents.into_iter().take(50).map(|(item, _)| item).collect()
|
recents.into_iter().take(50).map(|(item, _)| item).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_networks(sizes: IconSizes) -> Vec<Item> {
|
pub fn scan_network(uri: &str, mounters: Mounters, sizes: IconSizes) -> Vec<Item> {
|
||||||
//TODO: network folder items
|
for (key, mounter) in mounters.iter() {
|
||||||
vec![]
|
match mounter.network_scan(uri, sizes) {
|
||||||
|
Some(Ok(items)) => return items,
|
||||||
|
Some(Err(err)) => {
|
||||||
|
log::warn!("failed to scan networks: {}", err);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
|
@ -761,7 +770,7 @@ pub enum Location {
|
||||||
Search(PathBuf, String),
|
Search(PathBuf, String),
|
||||||
Trash,
|
Trash,
|
||||||
Recents,
|
Recents,
|
||||||
Networks,
|
Network(String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Location {
|
impl std::fmt::Display for Location {
|
||||||
|
|
@ -771,7 +780,7 @@ impl std::fmt::Display for Location {
|
||||||
Self::Search(path, term) => write!(f, "search {} for {}", path.display(), term),
|
Self::Search(path, term) => write!(f, "search {} for {}", path.display(), term),
|
||||||
Self::Trash => write!(f, "trash"),
|
Self::Trash => write!(f, "trash"),
|
||||||
Self::Recents => write!(f, "recents"),
|
Self::Recents => write!(f, "recents"),
|
||||||
Self::Networks => write!(f, "networks"),
|
Self::Network(uri, _) => write!(f, "{}", uri),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -785,13 +794,13 @@ impl Location {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan(&self, sizes: IconSizes) -> Vec<Item> {
|
pub fn scan(&self, mounters: Mounters, sizes: IconSizes) -> Vec<Item> {
|
||||||
match self {
|
match self {
|
||||||
Self::Path(path) => scan_path(path, sizes),
|
Self::Path(path) => scan_path(path, sizes),
|
||||||
Self::Search(path, term) => scan_search(path, term, sizes),
|
Self::Search(path, term) => scan_search(path, term, sizes),
|
||||||
Self::Trash => scan_trash(sizes),
|
Self::Trash => scan_trash(sizes),
|
||||||
Self::Recents => scan_recents(sizes),
|
Self::Recents => scan_recents(sizes),
|
||||||
Self::Networks => scan_networks(sizes),
|
Self::Network(uri, _) => scan_network(uri, mounters, sizes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -881,6 +890,12 @@ pub enum ItemMetadata {
|
||||||
metadata: trash::TrashItemMetadata,
|
metadata: trash::TrashItemMetadata,
|
||||||
entry: trash::TrashItem,
|
entry: trash::TrashItem,
|
||||||
},
|
},
|
||||||
|
SimpleDir {
|
||||||
|
entries: u64,
|
||||||
|
},
|
||||||
|
SimpleFile {
|
||||||
|
size: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ItemMetadata {
|
impl ItemMetadata {
|
||||||
|
|
@ -891,6 +906,8 @@ impl ItemMetadata {
|
||||||
trash::TrashItemSize::Entries(_) => true,
|
trash::TrashItemSize::Entries(_) => true,
|
||||||
trash::TrashItemSize::Bytes(_) => false,
|
trash::TrashItemSize::Bytes(_) => false,
|
||||||
},
|
},
|
||||||
|
Self::SimpleDir { .. } => true,
|
||||||
|
Self::SimpleFile { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1087,8 +1104,8 @@ impl Item {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemMetadata::Trash { .. } => {
|
_ => {
|
||||||
//TODO: trash metadata
|
//TODO: other metadata types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1127,8 +1144,8 @@ impl Item {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemMetadata::Trash { .. } => {
|
_ => {
|
||||||
//TODO: trash metadata
|
//TODO: other metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1278,9 +1295,7 @@ impl Tab {
|
||||||
Location::Recents => {
|
Location::Recents => {
|
||||||
fl!("recents")
|
fl!("recents")
|
||||||
}
|
}
|
||||||
Location::Networks => {
|
Location::Network(_uri, display_name) => display_name.clone(),
|
||||||
fl!("networks")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1592,14 +1607,18 @@ impl Tab {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|items| click_i_opt.and_then(|click_i| items.get(click_i)))
|
.and_then(|items| click_i_opt.and_then(|click_i| items.get(click_i)))
|
||||||
{
|
{
|
||||||
if let Some(Location::Path(path)) = &clicked_item.location_opt {
|
if let Some(location) = &clicked_item.location_opt {
|
||||||
if clicked_item.metadata.is_dir() {
|
if clicked_item.metadata.is_dir() {
|
||||||
cd = Some(Location::Path(path.clone()));
|
cd = Some(location.clone());
|
||||||
} else {
|
} else {
|
||||||
commands.push(Command::OpenFile(path.clone()));
|
if let Location::Path(path) = location {
|
||||||
|
commands.push(Command::OpenFile(path.clone()));
|
||||||
|
} else {
|
||||||
|
log::warn!("no path for item {:?}", clicked_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("no path for item {:?}", clicked_item);
|
log::warn!("no location for item {:?}", clicked_item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("no item for click index {:?}", click_i_opt);
|
log::warn!("no item for click index {:?}", click_i_opt);
|
||||||
|
|
@ -1990,12 +2009,14 @@ impl Tab {
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.selected {
|
if item.selected {
|
||||||
if let Some(Location::Path(path)) = &item.location_opt {
|
if let Some(location) = &item.location_opt {
|
||||||
if path.is_dir() {
|
if item.metadata.is_dir() {
|
||||||
//TODO: allow opening multiple tabs?
|
//TODO: allow opening multiple tabs?
|
||||||
cd = Some(Location::Path(path.clone()));
|
cd = Some(location.clone());
|
||||||
} else {
|
} else {
|
||||||
commands.push(Command::OpenFile(path.clone()));
|
if let Location::Path(path) = location {
|
||||||
|
commands.push(Command::OpenFile(path.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO: open properties?
|
//TODO: open properties?
|
||||||
|
|
@ -2112,20 +2133,11 @@ impl Tab {
|
||||||
}
|
}
|
||||||
commands.push(Command::DropFiles(to, from))
|
commands.push(Command::DropFiles(to, from))
|
||||||
}
|
}
|
||||||
Location::Search(_, _) => {
|
|
||||||
log::warn!(" Copy/cut to search not supported.");
|
|
||||||
}
|
|
||||||
Location::Trash if matches!(from.kind, ClipboardKind::Cut) => {
|
Location::Trash if matches!(from.kind, ClipboardKind::Cut) => {
|
||||||
commands.push(Command::MoveToTrash(from.paths))
|
commands.push(Command::MoveToTrash(from.paths))
|
||||||
}
|
}
|
||||||
Location::Trash => {
|
_ => {
|
||||||
log::warn!("Copy to trash is not supported.");
|
log::warn!("{:?} to {:?} is not supported.", from.kind, to);
|
||||||
}
|
|
||||||
Location::Recents => {
|
|
||||||
log::warn!("Copy to recents is not supported.");
|
|
||||||
}
|
|
||||||
Location::Networks => {
|
|
||||||
log::warn!("Copy to networks is not supported.");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2254,6 +2266,8 @@ impl Tab {
|
||||||
trash::TrashItemSize::Entries(entries) => (true, entries as u64),
|
trash::TrashItemSize::Entries(entries) => (true, entries as u64),
|
||||||
trash::TrashItemSize::Bytes(bytes) => (false, bytes),
|
trash::TrashItemSize::Bytes(bytes) => (false, bytes),
|
||||||
},
|
},
|
||||||
|
ItemMetadata::SimpleDir { entries } => (true, *entries),
|
||||||
|
ItemMetadata::SimpleFile { size } => (false, *size),
|
||||||
};
|
};
|
||||||
let (a_is_entry, a_size) = get_size(a.1);
|
let (a_is_entry, a_size) = get_size(a.1);
|
||||||
let (b_is_entry, b_size) = get_size(b.1);
|
let (b_is_entry, b_size) = get_size(b.1);
|
||||||
|
|
@ -2287,7 +2301,7 @@ impl Tab {
|
||||||
items.sort_by(|a, b| {
|
items.sort_by(|a, b| {
|
||||||
let get_modified = |x: &Item| match &x.metadata {
|
let get_modified = |x: &Item| match &x.metadata {
|
||||||
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
||||||
ItemMetadata::Trash { .. } => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let a_modified = get_modified(a.1);
|
let a_modified = get_modified(a.1);
|
||||||
|
|
@ -2588,11 +2602,14 @@ impl Tab {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Location::Networks => {
|
Location::Network(uri, display_name) => {
|
||||||
children.push(
|
children.push(
|
||||||
widget::button(widget::text::heading(fl!("networks")))
|
widget::button(widget::text::heading(display_name))
|
||||||
.padding(space_xxxs)
|
.padding(space_xxxs)
|
||||||
.on_press(Message::Location(Location::Networks))
|
.on_press(Message::Location(Location::Network(
|
||||||
|
uri.clone(),
|
||||||
|
display_name.clone(),
|
||||||
|
)))
|
||||||
.style(theme::Button::Text)
|
.style(theme::Button::Text)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
@ -3067,13 +3084,18 @@ impl Tab {
|
||||||
Ok(time) => format_time(time).to_string(),
|
Ok(time) => format_time(time).to_string(),
|
||||||
Err(_) => String::new(),
|
Err(_) => String::new(),
|
||||||
},
|
},
|
||||||
ItemMetadata::Trash { .. } => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let size_text = match &item.metadata {
|
let size_text = match &item.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path { metadata, children } => {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
format!("{} items", children)
|
//TODO: translate
|
||||||
|
if *children == 1 {
|
||||||
|
format!("{} item", children)
|
||||||
|
} else {
|
||||||
|
format!("{} items", children)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format_size(metadata.len())
|
format_size(metadata.len())
|
||||||
}
|
}
|
||||||
|
|
@ -3089,6 +3111,15 @@ impl Tab {
|
||||||
}
|
}
|
||||||
trash::TrashItemSize::Bytes(bytes) => format_size(bytes),
|
trash::TrashItemSize::Bytes(bytes) => format_size(bytes),
|
||||||
},
|
},
|
||||||
|
ItemMetadata::SimpleDir { entries } => {
|
||||||
|
//TODO: translate
|
||||||
|
if *entries == 1 {
|
||||||
|
format!("{} item", entries)
|
||||||
|
} else {
|
||||||
|
format!("{} items", entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ItemMetadata::SimpleFile { size } => format_size(*size),
|
||||||
};
|
};
|
||||||
|
|
||||||
let row = if condensed {
|
let row = if condensed {
|
||||||
|
|
@ -3410,7 +3441,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Location::Networks => {
|
Location::Network(uri, display_name) if uri == "network:///" => {
|
||||||
tab_column = tab_column.push(
|
tab_column = tab_column.push(
|
||||||
widget::layer_container(widget::row::with_children(vec![
|
widget::layer_container(widget::row::with_children(vec![
|
||||||
widget::horizontal_space(Length::Fill).into(),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue