chore: migrate to Rust 2024 edition
This commit is contained in:
parent
6ed7bdb45f
commit
f95869a631
16 changed files with 194 additions and 161 deletions
|
|
@ -2,9 +2,9 @@
|
|||
name = "cosmic-files"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
rust-version = "1.85.0"
|
||||
rust-version = "1.85"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "8", features = ["git", "gitcl"] }
|
||||
|
|
@ -88,7 +88,7 @@ optional = true
|
|||
git = "https://github.com/pop-os/libcosmic.git"
|
||||
default-features = false
|
||||
#TODO: a11y feature crashes
|
||||
features = ["autosize","multi-window", "tokio", "winit", "surface-message"]
|
||||
features = ["autosize", "multi-window", "tokio", "winit", "surface-message"]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "cosmic-files-applet"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
|
|
@ -11,4 +11,3 @@ zbus = "5"
|
|||
path = ".."
|
||||
default-features = false
|
||||
features = ["desktop", "gvfs", "wayland", "wgpu", "desktop-applet"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use cosmic_files::operation::recursive::Method;
|
||||
use cosmic_files::operation::{recursive::Context, Controller, ReplaceResult};
|
||||
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
|
||||
use std::{error::Error, io, path::PathBuf};
|
||||
|
||||
#[compio::main]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use cosmic::{
|
||||
Application, Element,
|
||||
app::{self, Core, Settings, Task},
|
||||
executor,
|
||||
iced::{window, Subscription},
|
||||
widget, Application, Element,
|
||||
iced::{Subscription, window},
|
||||
widget,
|
||||
};
|
||||
use cosmic_files::dialog::{
|
||||
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
||||
|
|
|
|||
62
src/app.rs
62
src/app.rs
|
|
@ -3,23 +3,24 @@
|
|||
|
||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||
use cosmic::iced::{
|
||||
Limits,
|
||||
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
|
||||
platform_specific::runtime::wayland::layer_surface::{
|
||||
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
||||
},
|
||||
platform_specific::shell::wayland::commands::layer_surface::{
|
||||
destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity, Layer,
|
||||
Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface,
|
||||
},
|
||||
Limits,
|
||||
};
|
||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||
use cosmic::iced_winit::commands::overlap_notify::overlap_notify;
|
||||
use cosmic::{
|
||||
app::{self, context_drawer, Core, Task},
|
||||
Application, ApplicationExt, Element,
|
||||
app::{self, Core, Task, context_drawer},
|
||||
cosmic_config::{self, ConfigSet},
|
||||
cosmic_theme, executor,
|
||||
iced::{
|
||||
self,
|
||||
self, Alignment, Event, Length, Point, Rectangle, Size, Subscription,
|
||||
clipboard::dnd::DndAction,
|
||||
core::SmolStr,
|
||||
event,
|
||||
|
|
@ -28,7 +29,6 @@ use cosmic::{
|
|||
stream,
|
||||
widget::scrollable,
|
||||
window::{self, Event as WindowEvent, Id as WindowId},
|
||||
Alignment, Event, Length, Point, Rectangle, Size, Subscription,
|
||||
},
|
||||
iced_runtime::clipboard,
|
||||
style, surface, theme,
|
||||
|
|
@ -40,13 +40,11 @@ use cosmic::{
|
|||
segmented_button::{self, Entity},
|
||||
vertical_space,
|
||||
},
|
||||
Application, ApplicationExt, Element,
|
||||
};
|
||||
use mime_guess::Mime;
|
||||
use notify_debouncer_full::{
|
||||
new_debouncer,
|
||||
DebouncedEvent, Debouncer, FileIdMap, new_debouncer,
|
||||
notify::{self, RecommendedWatcher, Watcher},
|
||||
DebouncedEvent, Debouncer, FileIdMap,
|
||||
};
|
||||
use slotmap::Key as SlotMapKey;
|
||||
use std::{
|
||||
|
|
@ -65,13 +63,13 @@ use std::{
|
|||
use tokio::sync::mpsc;
|
||||
use trash::TrashItem;
|
||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||
use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
||||
use wayland_client::{Proxy, protocol::wl_output::WlOutput};
|
||||
|
||||
use crate::{
|
||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||
config::{
|
||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TimeConfig, TypeToSearch,
|
||||
TIME_CONFIG_ID,
|
||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, TIME_CONFIG_ID, TabConfig,
|
||||
TimeConfig, TypeToSearch,
|
||||
},
|
||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult},
|
||||
fl, home_dir,
|
||||
|
|
@ -80,14 +78,14 @@ use crate::{
|
|||
menu,
|
||||
mime_app::{self, MimeApp, MimeAppCache},
|
||||
mime_icon,
|
||||
mounter::{MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
||||
mounter::{MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||
operation::{
|
||||
Controller, Operation, OperationError, OperationErrorType, OperationSelection,
|
||||
ReplaceResult,
|
||||
},
|
||||
spawn_detached::spawn_detached,
|
||||
tab::{
|
||||
self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION, SORT_OPTION_FALLBACK,
|
||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, Tab,
|
||||
},
|
||||
};
|
||||
use crate::{config::State, dialog::DialogSettings};
|
||||
|
|
@ -2195,7 +2193,7 @@ impl Application for App {
|
|||
for location in flags.uris {
|
||||
if let Some(e) = app.nav_model.iter().find(|e| {
|
||||
app.nav_model.data::<Location>(*e).is_some_and(
|
||||
|l| matches!(l, Location::Network(ref uri, ..) if *uri == location.to_string()),
|
||||
|l| matches!(l, Location::Network(uri, ..) if *uri == location.to_string()),
|
||||
)
|
||||
}) {
|
||||
commands.push(cosmic::task::message(cosmic::Action::App(
|
||||
|
|
@ -3194,7 +3192,11 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Err(err) => {
|
||||
log::warn!("failed to reload metadata for {:?}: {}", path, err);
|
||||
log::warn!(
|
||||
"failed to reload metadata for {:?}: {}",
|
||||
path,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
//TODO item.thumbnail_opt =
|
||||
|
|
@ -3283,7 +3285,7 @@ impl Application for App {
|
|||
None
|
||||
}
|
||||
},
|
||||
))
|
||||
));
|
||||
}
|
||||
Message::OpenInNewWindow(entity_opt) => match env::current_exe() {
|
||||
Ok(exe) => self
|
||||
|
|
@ -3307,7 +3309,7 @@ impl Application for App {
|
|||
self.open_tab(Location::Path(parent), true, Some(vec![path]))
|
||||
})
|
||||
},
|
||||
))
|
||||
));
|
||||
}
|
||||
Message::OpenWithBrowse => match self.dialog_pages.pop_front() {
|
||||
Some((
|
||||
|
|
@ -4428,14 +4430,14 @@ impl Application for App {
|
|||
}
|
||||
NavMenuAction::OpenInNewTab(entity) => {
|
||||
match self.nav_model.data::<Location>(entity) {
|
||||
Some(Location::Network(ref uri, ref display_name, path)) => {
|
||||
Some(Location::Network(uri, display_name, path)) => {
|
||||
return self.open_tab(
|
||||
Location::Network(uri.clone(), display_name.clone(), path.clone()),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
}
|
||||
Some(Location::Path(ref path)) => {
|
||||
Some(Location::Path(path)) => {
|
||||
return self.open_tab(Location::Path(path.clone()), false, None);
|
||||
}
|
||||
Some(Location::Recents) => {
|
||||
|
|
@ -5721,7 +5723,7 @@ impl Application for App {
|
|||
.map(|x| Message::TabMessage(Some(*entity), x)),
|
||||
id.clone(),
|
||||
)
|
||||
.into()
|
||||
.into();
|
||||
}
|
||||
None => widget::text("Unknown tab ID").into(),
|
||||
}
|
||||
|
|
@ -5977,7 +5979,9 @@ impl Application for App {
|
|||
if let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanTrash).await
|
||||
}) {
|
||||
log::warn!("trash needs to be rescanned but sending message failed: {e:?}");
|
||||
log::warn!(
|
||||
"trash needs to be rescanned but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6062,7 +6066,9 @@ impl Application for App {
|
|||
if let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanRecents).await
|
||||
}) {
|
||||
log::warn!("open recents tabs need to be updated but sending message failed: {e:?}");
|
||||
log::warn!(
|
||||
"open recents tabs need to be updated but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6215,7 +6221,7 @@ pub(crate) mod test_utils {
|
|||
};
|
||||
|
||||
use log::{debug, trace};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use tempfile::{TempDir, tempdir};
|
||||
|
||||
use crate::{
|
||||
config::{IconSizes, TabConfig, ThumbCfg},
|
||||
|
|
@ -6274,7 +6280,9 @@ pub(crate) mod test_utils {
|
|||
// TempDir won't leak resources as long as the destructor runs
|
||||
let root = tempdir()?;
|
||||
debug!("Root temp directory: {}", root.as_ref().display());
|
||||
trace!("Creating {files} files and {hidden} hidden files in {dirs} temp dirs with {nested} nested temp dirs");
|
||||
trace!(
|
||||
"Creating {files} files and {hidden} hidden files in {dirs} temp dirs with {nested} nested temp dirs"
|
||||
);
|
||||
|
||||
// All paths for directories and nested directories
|
||||
let paths = (0..dirs).flat_map(|_| {
|
||||
|
|
@ -6349,11 +6357,7 @@ pub(crate) mod test_utils {
|
|||
Ok(path.read_dir()?.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if path.is_dir() { Some(path) } else { None }
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
|
||||
|
||||
use cosmic::{
|
||||
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
||||
Application,
|
||||
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
||||
iced::Subscription,
|
||||
theme, Application,
|
||||
theme,
|
||||
};
|
||||
use ordermap::OrderMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
|||
|
|
@ -2,26 +2,25 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
app::{context_drawer, cosmic::Cosmic, Core, Task},
|
||||
Application, ApplicationExt, Element,
|
||||
app::{Core, Task, context_drawer, cosmic::Cosmic},
|
||||
cosmic_config, cosmic_theme, executor,
|
||||
iced::{
|
||||
self, event,
|
||||
self, Alignment, Event, Length, Size, Subscription, event,
|
||||
futures::{self, SinkExt},
|
||||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
||||
stream, window, Alignment, Event, Length, Size, Subscription,
|
||||
stream, window,
|
||||
},
|
||||
theme,
|
||||
widget::{
|
||||
self,
|
||||
menu::{key_bind::Modifier, Action as MenuAction, KeyBind},
|
||||
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
|
||||
segmented_button,
|
||||
},
|
||||
Application, ApplicationExt, Element,
|
||||
};
|
||||
use notify_debouncer_full::{
|
||||
new_debouncer,
|
||||
DebouncedEvent, Debouncer, FileIdMap, new_debouncer,
|
||||
notify::{self, RecommendedWatcher, Watcher},
|
||||
DebouncedEvent, Debouncer, FileIdMap,
|
||||
};
|
||||
use recently_used_xbel::update_recently_used;
|
||||
use std::{
|
||||
|
|
@ -36,12 +35,12 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
|
||||
config::{Config, DialogConfig, Favorite, ThumbCfg, TimeConfig, TIME_CONFIG_ID},
|
||||
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig},
|
||||
fl, home_dir,
|
||||
key_bind::key_binds,
|
||||
localize::LANGUAGE_SORTER,
|
||||
menu,
|
||||
mounter::{MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
||||
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||
tab::{self, ItemMetadata, Location, Tab},
|
||||
};
|
||||
|
||||
|
|
@ -1452,7 +1451,11 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to reload metadata for {:?}: {}", path, err);
|
||||
log::warn!(
|
||||
"failed to reload metadata for {:?}: {}",
|
||||
path,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
//TODO item.thumbnail_opt =
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use i18n_embed::{
|
||||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||
};
|
||||
use icu::locid::Locale;
|
||||
use icu_collator::{Collator, CollatorOptions, Numeric};
|
||||
|
|
|
|||
12
src/menu.rs
12
src/menu.rs
|
|
@ -1,18 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
Element,
|
||||
app::Core,
|
||||
iced::{
|
||||
advanced::widget::text::Style as TextStyle, keyboard::Modifiers, Alignment, Background,
|
||||
Border, Length,
|
||||
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
|
||||
keyboard::Modifiers,
|
||||
},
|
||||
theme,
|
||||
widget::{
|
||||
self, button, column, container, divider, horizontal_space,
|
||||
menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
|
||||
responsive_menu_bar, text, Row,
|
||||
self, Row, button, column, container, divider, horizontal_space,
|
||||
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||
responsive_menu_bar, text,
|
||||
},
|
||||
Element,
|
||||
};
|
||||
use i18n_embed::LanguageLoader;
|
||||
use mime_guess::Mime;
|
||||
|
|
|
|||
|
|
@ -346,7 +346,11 @@ impl MimeAppCache {
|
|||
{
|
||||
apps.push(MimeApp::from(app));
|
||||
} else {
|
||||
log::info!("failed to add association for {:?}: application {:?} not found", mime, filename);
|
||||
log::info!(
|
||||
"failed to add association for {:?}: application {:?} not found",
|
||||
mime,
|
||||
filename
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -385,7 +389,11 @@ impl MimeAppCache {
|
|||
if found {
|
||||
break;
|
||||
} else {
|
||||
log::debug!("failed to set default for {:?}: application {:?} not found", mime, filename);
|
||||
log::debug!(
|
||||
"failed to set default for {:?}: application {:?} not found",
|
||||
mime,
|
||||
filename
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -625,10 +633,12 @@ mod tests {
|
|||
let command = commands.first().unwrap();
|
||||
|
||||
assert_eq!("/usr/games/gzdoom", command.get_program().to_str().unwrap());
|
||||
assert!(paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
||||
assert!(
|
||||
paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -669,10 +679,12 @@ mod tests {
|
|||
assert_eq!(paths.len(), command.get_args().count());
|
||||
|
||||
assert_eq!("/usr/bin/mpv", command.get_program().to_str().unwrap());
|
||||
assert!(paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
||||
assert!(
|
||||
paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -695,11 +707,12 @@ mod tests {
|
|||
assert_eq!(args.len() + paths.len(), command.get_args().count());
|
||||
|
||||
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
||||
assert!(args
|
||||
.iter()
|
||||
.chain(paths.iter())
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
||||
assert!(
|
||||
args.iter()
|
||||
.chain(paths.iter())
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -719,10 +732,12 @@ mod tests {
|
|||
"/usr/games/roguelike",
|
||||
command.get_program().to_str().unwrap()
|
||||
);
|
||||
assert!(paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
||||
assert!(
|
||||
paths
|
||||
.iter()
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -751,11 +766,13 @@ mod tests {
|
|||
);
|
||||
|
||||
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
||||
assert!(args_leading
|
||||
.iter()
|
||||
.chain(paths.iter())
|
||||
.chain(args_trailing.iter())
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
||||
assert!(
|
||||
args_leading
|
||||
.iter()
|
||||
.chain(paths.iter())
|
||||
.chain(args_trailing.iter())
|
||||
.zip(command.get_args())
|
||||
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use cosmic::{
|
||||
iced::{futures::SinkExt, stream, Subscription},
|
||||
widget, Task,
|
||||
Task,
|
||||
iced::{Subscription, futures::SinkExt, stream},
|
||||
widget,
|
||||
};
|
||||
use gio::{glib, prelude::*};
|
||||
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||
use crate::{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use cosmic::{iced::Subscription, widget, Task};
|
||||
use cosmic::{Task, iced::Subscription, widget};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use std::time::Instant;
|
|||
|
||||
use crate::tab::DOUBLE_CLICK_DURATION;
|
||||
use cosmic::{
|
||||
Element, Renderer, Theme,
|
||||
iced_core::{
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
border::Border,
|
||||
event::{self, Event},
|
||||
layout,
|
||||
|
|
@ -12,11 +14,9 @@ use cosmic::{
|
|||
overlay,
|
||||
renderer::{self, Quad, Renderer as _},
|
||||
touch,
|
||||
widget::{tree, Operation, Tree},
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
widget::{Operation, Tree, tree},
|
||||
},
|
||||
widget::Id,
|
||||
Element, Renderer, Theme,
|
||||
};
|
||||
|
||||
/// Emit messages on mouse events.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
spawn_detached::spawn_detached,
|
||||
tab,
|
||||
};
|
||||
use cosmic::iced::futures::{channel::mpsc::Sender, SinkExt};
|
||||
use cosmic::iced::futures::{SinkExt, channel::mpsc::Sender};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Formatter,
|
||||
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::{mpsc, Mutex as TokioMutex};
|
||||
use tokio::sync::{Mutex as TokioMutex, mpsc};
|
||||
use walkdir::WalkDir;
|
||||
use zip::AesMode::Aes256;
|
||||
|
||||
|
|
@ -140,9 +140,11 @@ async fn copy_or_move(
|
|||
Ok(()) => {
|
||||
log::info!("renamed {from:?} to {to:?}");
|
||||
false
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!("failed to rename {from:?} to {to:?}, fallback to recursive move: {err}");
|
||||
log::info!(
|
||||
"failed to rename {from:?} to {to:?}, fallback to recursive move: {err}"
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -1155,7 +1157,7 @@ mod tests {
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use cosmic::iced::futures::{channel::mpsc, StreamExt};
|
||||
use cosmic::iced::futures::{StreamExt, channel::mpsc};
|
||||
use log::debug;
|
||||
use test_log::test;
|
||||
use tokio::sync;
|
||||
|
|
@ -1163,11 +1165,11 @@ mod tests {
|
|||
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
|
||||
use crate::{
|
||||
app::{
|
||||
test_utils::{
|
||||
empty_fs, filter_dirs, filter_files, simple_fs, NAME_LEN, NUM_DIRS, NUM_FILES,
|
||||
NUM_HIDDEN, NUM_NESTED,
|
||||
},
|
||||
DialogPage, Message,
|
||||
test_utils::{
|
||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
|
||||
filter_files, simple_fs,
|
||||
},
|
||||
},
|
||||
fl,
|
||||
};
|
||||
|
|
@ -1198,10 +1200,13 @@ mod tests {
|
|||
match msg {
|
||||
Message::DialogPush(DialogPage::Replace { tx, .. }) => {
|
||||
debug!("[{id}] Replace request");
|
||||
tx.send(ReplaceResult::Cancel).await.expect("Sending a response to a replace request should succeed")
|
||||
|
||||
tx.send(ReplaceResult::Cancel)
|
||||
.await
|
||||
.expect("Sending a response to a replace request should succeed")
|
||||
}
|
||||
_ => unreachable!("Only [ `Message::PendingProgress`, `Message::DialogPush(DialogPage::Replace)` ] are sent from operation"),
|
||||
_ => unreachable!(
|
||||
"Only [ `Message::PendingProgress`, `Message::DialogPush(DialogPage::Replace)` ] are sent from operation"
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use compio::BufResult;
|
||||
use compio::buf::{IntoInner, IoBuf};
|
||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||
use compio::BufResult;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::time::Instant;
|
||||
|
|
@ -9,7 +9,7 @@ use walkdir::WalkDir;
|
|||
|
||||
use crate::operation::OperationError;
|
||||
|
||||
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
|
||||
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||
|
||||
pub enum Method {
|
||||
Copy,
|
||||
|
|
|
|||
120
src/tab.rs
120
src/tab.rs
|
|
@ -1,6 +1,16 @@
|
|||
use cosmic::{
|
||||
cosmic_theme, font,
|
||||
Element, cosmic_theme, font,
|
||||
iced::{
|
||||
Alignment,
|
||||
Border,
|
||||
Color,
|
||||
ContentFit,
|
||||
Length,
|
||||
Point,
|
||||
Rectangle,
|
||||
Size,
|
||||
Subscription,
|
||||
Vector,
|
||||
advanced::{
|
||||
graphics,
|
||||
text::{self, Paragraph},
|
||||
|
|
@ -17,36 +27,24 @@ use cosmic::{
|
|||
scrollable::{self, AbsoluteOffset, Viewport},
|
||||
},
|
||||
window,
|
||||
Alignment,
|
||||
Border,
|
||||
Color,
|
||||
ContentFit,
|
||||
Length,
|
||||
Point,
|
||||
Rectangle,
|
||||
Size,
|
||||
Subscription,
|
||||
Vector,
|
||||
},
|
||||
iced_core::{mouse::ScrollDelta, widget::tree},
|
||||
theme,
|
||||
widget::{
|
||||
self,
|
||||
self, DndDestination, DndSource, Id, Space, Widget,
|
||||
menu::{action::MenuAction, key_bind::KeyBind},
|
||||
DndDestination, DndSource, Id, Space, Widget,
|
||||
},
|
||||
Element,
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||
use i18n_embed::LanguageLoader;
|
||||
use icu::datetime::{
|
||||
options::{components, preferences},
|
||||
DateTimeFormatter, DateTimeFormatterOptions,
|
||||
options::{components, preferences},
|
||||
};
|
||||
use image::ImageDecoder;
|
||||
use jxl_oxide::integration::JxlDecoder;
|
||||
use mime_guess::{mime, Mime};
|
||||
use mime_guess::{Mime, mime};
|
||||
use once_cell::sync::Lazy;
|
||||
use ordermap::OrderMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -62,7 +60,7 @@ use std::{
|
|||
io::{BufRead, BufReader},
|
||||
os::unix::fs::MetadataExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic, Arc, LazyLock, Mutex, RwLock},
|
||||
sync::{Arc, LazyLock, Mutex, RwLock, atomic},
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
|
|
@ -73,7 +71,7 @@ use walkdir::WalkDir;
|
|||
use crate::{
|
||||
app::{Action, PreviewItem, PreviewKind},
|
||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||
config::{DesktopConfig, IconSizes, TabConfig, ThumbCfg, ICON_SCALE_MAX, ICON_SIZE_GRID},
|
||||
config::{DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig, ThumbCfg},
|
||||
dialog::DialogKind,
|
||||
fl,
|
||||
localize::{LANGUAGE_SORTER, LOCALE},
|
||||
|
|
@ -4020,11 +4018,7 @@ impl Tab {
|
|||
|
||||
fn column_sort(&self) -> Option<Vec<(usize, &Item)>> {
|
||||
let check_reverse = |ord: Ordering, sort: bool| {
|
||||
if sort {
|
||||
ord
|
||||
} else {
|
||||
ord.reverse()
|
||||
}
|
||||
if sort { ord } else { ord.reverse() }
|
||||
};
|
||||
let mut items: Vec<_> = self.items_opt.as_ref()?.iter().enumerate().collect();
|
||||
let (sort_name, sort_direction, folders_first) = self.sort_options();
|
||||
|
|
@ -4658,29 +4652,31 @@ impl Tab {
|
|||
pub fn empty_view(&self, has_hidden: bool) -> Element<Message> {
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
mouse_area::MouseArea::new(widget::column::with_children(vec![widget::container(
|
||||
widget::column::with_children(match self.mode {
|
||||
Mode::App | Mode::Dialog(_) => vec![
|
||||
widget::icon::from_name("folder-symbolic")
|
||||
.size(64)
|
||||
.icon()
|
||||
mouse_area::MouseArea::new(widget::column::with_children(vec![
|
||||
widget::container(
|
||||
widget::column::with_children(match self.mode {
|
||||
Mode::App | Mode::Dialog(_) => vec![
|
||||
widget::icon::from_name("folder-symbolic")
|
||||
.size(64)
|
||||
.icon()
|
||||
.into(),
|
||||
widget::text::body(if has_hidden {
|
||||
fl!("empty-folder-hidden")
|
||||
} else if matches!(self.location, Location::Search(..)) {
|
||||
fl!("no-results")
|
||||
} else {
|
||||
fl!("empty-folder")
|
||||
})
|
||||
.into(),
|
||||
widget::text::body(if has_hidden {
|
||||
fl!("empty-folder-hidden")
|
||||
} else if matches!(self.location, Location::Search(..)) {
|
||||
fl!("no-results")
|
||||
} else {
|
||||
fl!("empty-folder")
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
Mode::Desktop => Vec::new(),
|
||||
})
|
||||
.align_x(Alignment::Center)
|
||||
.spacing(space_xxs),
|
||||
)
|
||||
.center(Length::Fill)
|
||||
.into()]))
|
||||
],
|
||||
Mode::Desktop => Vec::new(),
|
||||
})
|
||||
.align_x(Alignment::Center)
|
||||
.spacing(space_xxs),
|
||||
)
|
||||
.center(Length::Fill)
|
||||
.into(),
|
||||
]))
|
||||
.on_press(|_| Message::Click(None))
|
||||
.into()
|
||||
}
|
||||
|
|
@ -6109,11 +6105,11 @@ mod tests {
|
|||
use tempfile::TempDir;
|
||||
use test_log::test;
|
||||
|
||||
use super::{respond_to_scroll_direction, scan_path, Location, Message, Tab};
|
||||
use super::{Location, Message, Tab, respond_to_scroll_direction, scan_path};
|
||||
use crate::{
|
||||
app::test_utils::{
|
||||
assert_eq_tab_path, empty_fs, eq_path_item, filter_dirs, read_dir_sorted, simple_fs,
|
||||
tab_click_new, NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED,
|
||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs,
|
||||
eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new,
|
||||
},
|
||||
config::{IconSizes, TabConfig, ThumbCfg},
|
||||
};
|
||||
|
|
@ -6166,12 +6162,16 @@ mod tests {
|
|||
);
|
||||
|
||||
// All directories (simple_fs only produces one nested layer)
|
||||
let dirs: Vec<PathBuf> = filter_dirs(path)?
|
||||
.flat_map(|dir| {
|
||||
filter_dirs(&dir).map(|nested_dirs| std::iter::once(dir).chain(nested_dirs))
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
let dirs: Vec<PathBuf> = {
|
||||
let top_level = filter_dirs(path)?;
|
||||
let mut result = Vec::new();
|
||||
for dir in top_level {
|
||||
let nested_dirs: Vec<PathBuf> = filter_dirs(&dir)?.collect();
|
||||
result.push(dir);
|
||||
result.extend(nested_dirs);
|
||||
}
|
||||
result
|
||||
};
|
||||
assert!(
|
||||
dirs.len() == NUM_DIRS + NUM_DIRS * NUM_NESTED,
|
||||
"Sanity check: Have {} dirs instead of {}",
|
||||
|
|
@ -6210,10 +6210,12 @@ mod tests {
|
|||
assert_eq!(entries.len(), actual.len());
|
||||
|
||||
// Correct files should be scanned
|
||||
assert!(entries
|
||||
.into_iter()
|
||||
.zip(actual.into_iter())
|
||||
.all(|(path, item)| eq_path_item(&path, &item)));
|
||||
assert!(
|
||||
entries
|
||||
.into_iter()
|
||||
.zip(actual.into_iter())
|
||||
.all(|(path, item)| eq_path_item(&path, &item))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -6483,7 +6485,7 @@ mod tests {
|
|||
#[test]
|
||||
fn mode_calculations() {
|
||||
use super::{
|
||||
get_mode_part, set_mode_part, MODE_SHIFT_GROUP, MODE_SHIFT_OTHER, MODE_SHIFT_USER,
|
||||
MODE_SHIFT_GROUP, MODE_SHIFT_OTHER, MODE_SHIFT_USER, get_mode_part, set_mode_part,
|
||||
};
|
||||
for user in 0..=7 {
|
||||
for group in 0..=7 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue