diff --git a/res/icons/bundled/harddisk-symbolic.svg b/res/icons/bundled/harddisk-symbolic.svg new file mode 100644 index 0000000..75214bd --- /dev/null +++ b/res/icons/bundled/harddisk-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/res/icons/bundled/timer-sand-symbolic.svg b/res/icons/bundled/timer-sand-symbolic.svg new file mode 100644 index 0000000..cab8855 --- /dev/null +++ b/res/icons/bundled/timer-sand-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/app.rs b/src/app.rs index bd08a1f..bee1259 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,7 +5,7 @@ use std::collections::{HashMap, VecDeque}; use std::path::PathBuf; use std::{env, process}; -use cosmic::app::{Command, Core}; +use cosmic::app::{message, Command, Core}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::{event, keyboard::Event as KeyEvent, window, Event, Subscription}; use cosmic::iced_core::keyboard::{Key, Modifiers}; @@ -24,7 +24,10 @@ use cosmic::{widget, Application, Apply, Element}; use crate::app::config::{AppTheme, Repository, CONFIG_VERSION}; use crate::fl; +use self::icon_cache::IconCache; + pub mod config; +pub mod icon_cache; pub mod menu; pub mod settings; @@ -62,7 +65,7 @@ pub enum Message { Modifiers(Modifiers), WindowClose, WindowNew, - CreateRepository(String), + Repository(RepositoryAction), CreateSnapshot, OpenCreateRepositoryDialog, OpenCreateSnapshotDialog, @@ -70,6 +73,13 @@ pub enum Message { DeleteSnapshotDialog, } +#[derive(Debug, Clone)] +pub enum RepositoryAction { + Init(String), + Created(Repository), + Error(String), +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ContextPage { About, @@ -183,11 +193,17 @@ impl App { .into() } - fn create_nav_item(&mut self, repository: Repository) -> EntityMut { + fn create_nav_item( + &mut self, + repository: Repository, + icon: &'static str, + ) -> EntityMut { self.nav_model .insert() + .icon(IconCache::get(icon, 18)) .text(repository.name.clone()) .data(repository.clone()) + .activate() } } @@ -235,7 +251,7 @@ impl Application for App { let repositories = app.config.repositories.clone(); for repository in repositories { - app.create_nav_item(repository); + app.create_nav_item(repository, "harddisk-symbolic"); } (app, Command::none()) @@ -425,24 +441,42 @@ impl Application for App { Message::OpenCreateSnapshotDialog => { self.dialog_pages.push_back(DialogPage::CreateSnapshot); } - Message::CreateRepository(path) => match crate::backup::init(&path, "password") { - Ok(_) => { - let path = PathBuf::from(&path); - let name = path + Message::Repository(state) => match state { + RepositoryAction::Init(path) => { + let init_path = path.clone(); + let name = PathBuf::from(&path) .file_name() .unwrap_or_default() .to_string_lossy() .to_string(); + let repository = Repository { + name, + path: PathBuf::from(&path), + }; + self.create_nav_item(repository.clone(), "timer-sand-symbolic"); + return Command::perform( + async move { crate::backup::init(&init_path, "password") }, + |result| match result { + Ok(_) => message::app(Message::Repository(RepositoryAction::Created( + repository, + ))), + Err(e) => message::app(Message::Repository(RepositoryAction::Error( + e.to_string(), + ))), + }, + ); + } + RepositoryAction::Created(repository) => { + if self.nav_model.active_data::().is_some() { + let entity = self.nav_model.active(); + self.nav_model + .icon_set(entity, IconCache::get("harddisk-symbolic", 18)); + } let mut repositories = self.config.repositories.clone(); - let repository = Repository { name, path }; - repositories.push(repository.clone()); - self.create_nav_item(repository); + repositories.push(repository); config_set!(repositories, repositories); } - Err(e) => { - // TODO: Show error to user. - eprintln!("failed to create repository: {}", e) - } + RepositoryAction::Error(error) => log::error!("{}", error), }, Message::DeleteRepositoryDialog => { println!("Deleting repository"); @@ -470,8 +504,8 @@ impl Application for App { Message::DialogComplete => { if let Some(dialog_page) = self.dialog_pages.pop_front() { match dialog_page { - DialogPage::CreateRepository(name) => { - return self.update(Message::CreateRepository(name)); + DialogPage::CreateRepository(path) => { + return self.update(Message::Repository(RepositoryAction::Init(path))); } DialogPage::CreateSnapshot => { return self.update(Message::CreateSnapshot); diff --git a/src/app/icon_cache.rs b/src/app/icon_cache.rs new file mode 100644 index 0000000..97e2202 --- /dev/null +++ b/src/app/icon_cache.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::widget::icon; +use std::collections::HashMap; +use std::sync::{Mutex, OnceLock}; + +pub(crate) static ICON_CACHE: OnceLock> = OnceLock::new(); + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct IconCacheKey { + name: &'static str, + size: u16, +} + +pub struct IconCache { + cache: HashMap, +} + +impl IconCache { + pub fn new() -> Self { + let mut cache = HashMap::new(); + + macro_rules! bundle { + ($name:expr, $size:expr) => { + let data: &'static [u8] = + include_bytes!(concat!("../../res/icons/bundled/", $name, ".svg")); + cache.insert( + IconCacheKey { + name: $name, + size: $size, + }, + icon::from_svg_bytes(data).symbolic(true), + ); + }; + } + + bundle!("timer-sand-symbolic", 18); + bundle!("harddisk-symbolic", 18); + + Self { cache } + } + + fn get_icon(&mut self, name: &'static str, size: u16) -> icon::Icon { + let handle = self + .cache + .entry(IconCacheKey { name, size }) + .or_insert_with(|| icon::from_name(name).size(size).handle()) + .clone(); + icon::icon(handle).size(size) + } + + pub fn get(name: &'static str, size: u16) -> icon::Icon { + let mut icon_cache = ICON_CACHE.get().unwrap().lock().unwrap(); + icon_cache.get_icon(name, size) + } +} diff --git a/src/app/settings.rs b/src/app/settings.rs index be31310..bd24c87 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -1,10 +1,14 @@ +use std::sync::Mutex; + use super::config::CosmicBackupsConfig; +use super::icon_cache::{IconCache, ICON_CACHE}; use crate::app::Flags; use cosmic::app::Settings; use cosmic::iced::{Limits, Size}; pub fn init() -> (Settings, Flags) { set_logger(); + set_icon_cache(); let settings = get_app_settings(); let flags = get_flags(); (settings, flags) @@ -25,6 +29,10 @@ pub fn set_logger() { tracing_subscriber::fmt().json().init(); } +pub fn set_icon_cache() { + ICON_CACHE.get_or_init(|| Mutex::new(IconCache::new())); +} + pub fn get_flags() -> Flags { let (config_handler, config) = ( CosmicBackupsConfig::config_handler(), diff --git a/src/backup/restore.rs b/src/backup/restore.rs index ce694d0..0aa86ee 100644 --- a/src/backup/restore.rs +++ b/src/backup/restore.rs @@ -2,6 +2,7 @@ use rustic_backend::BackendOptions; use rustic_core::{LocalDestination, LsOptions, Repository, RepositoryOptions, RestoreOptions}; use std::error::Error; +#[allow(dead_code)] pub fn restore( repository: &str, password: &str,