From fcb9297fdc4c930b4fb049e24d3d541273c541b7 Mon Sep 17 00:00:00 2001 From: fromost Date: Mon, 10 Nov 2025 02:02:14 +0800 Subject: [PATCH] Refactor code to remove Any --- Cargo.toml | 2 +- src/app.rs | 50 ++++++++----------- src/cli/mod.rs | 2 +- src/cli/sync.rs | 5 +- src/config/mod.rs | 16 +++--- src/config/types.rs | 1 - src/crawler/dlsite.rs | 5 +- src/crawler/mod.rs | 1 - src/event.rs | 4 +- src/helpers/db.rs | 7 +-- src/helpers/mod.rs | 9 ++-- src/widgets/popups/mod.rs | 4 ++ src/widgets/views/main_view.rs | 89 ++++++++++++++++++---------------- src/widgets/views/mod.rs | 7 ++- 14 files changed, 98 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb2c50c..ef47d22 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ description = "manager for SuS gamers" authors = ["fromost"] license = "MIT" edition = "2024" +rust-version = "1.91" [profile.dev] debug = true @@ -30,7 +31,6 @@ log = "0.4.28" num_cpus = "1.17.0" sys-locale = "0.3.2" jemallocator = "0.5.4" -reqwest_cookie_store = { version = "0.9.0", features = ["serde"] } itertools = "0.14.0" dashmap = { version = "6.1.0", features = ["serde"] } diff --git a/src/app.rs b/src/app.rs index 8c1d24a..0938bed 100755 --- a/src/app.rs +++ b/src/app.rs @@ -1,13 +1,11 @@ use crate::config::types::ApplicationConfig; use crate::event::{AppEvent, EventHandler}; -use crate::widgets::views::MainView; +use crate::widgets::views::{AppView, MainView}; use crate::widgets::views::View; use color_eyre::Result; -use crossterm::event::Event as CrosstermEvent; -use crossterm::event::{Event, KeyEvent}; +use crossterm::event::{Event}; use rat_cursor::HasScreenCursor; use ratatui::{DefaultTerminal, Frame}; -use std::any::Any; use std::time::Duration; pub(crate) struct App { @@ -16,14 +14,14 @@ pub(crate) struct App { } struct AppState { - view: Option>, + view: Option, } impl App { pub async fn create() -> Result { let config = ApplicationConfig::get_config()?; let state = AppState { - view: Some(Box::new(MainView::new())), + view: Some(AppView::MainView(MainView::new())), }; let app = Self { events: EventHandler::new(Duration::from_millis(config.basic_config.tick_rate)), @@ -38,7 +36,7 @@ impl App { let event = self.events.next().await?; self.update(event)?; if let Some(view) = self.state.view.as_mut() - && let Some(main_view) = view.downcast_ref::() + && let AppView::MainView(main_view) = view && !main_view.is_running() { break Ok(()); @@ -49,27 +47,17 @@ impl App { fn update(&mut self, event: AppEvent) -> Result<()> { if let AppEvent::Raw(cross_event) = event { self.handle_event(&cross_event)?; - if let CrosstermEvent::Key(key) = cross_event { - self.handle_key_event(&key)?; - } } Ok(()) } - fn handle_key_event(&mut self, key: &KeyEvent) -> Result<()> { - if let Some(any) = self.state.view.as_mut() { - if let Some(main_view) = any.downcast_mut::() { - main_view.handle_key_input(key)?; - } - } - Ok(()) - } - fn handle_event(&mut self, key: &Event) -> Result<()> { - if let Some(any) = self.state.view.as_mut() { - if let Some(main_view) = any.downcast_mut::() { - main_view.handle_input(key)?; + if let Some(current_view) = self.state.view.as_mut() { + match current_view { + AppView::MainView(main_view) => { + main_view.handle_input(key)? + } } } Ok(()) @@ -77,14 +65,16 @@ impl App { fn draw(&mut self, frame: &mut Frame) { if let Some(view) = self.state.view.as_mut() { - if let Some(main_view) = view.downcast_mut::() { - frame.render_stateful_widget( - MainView::new(), - frame.area(), - &mut main_view.state, - ); - if let Some(pos) = main_view.screen_cursor() { - frame.set_cursor_position(pos); + match view { + AppView::MainView(main_view) => { + frame.render_stateful_widget( + MainView::new(), + frame.area(), + &mut main_view.state, + ); + if let Some(pos) = main_view.screen_cursor() { + frame.set_cursor_position(pos); + } } } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 267b22c..b5cd930 100755 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -2,7 +2,7 @@ mod folder; mod sync; use crate::{app, helpers}; -use clap::{command, Args, Command, Parser, Subcommand}; +use clap::{command, Parser}; use color_eyre::Result; use ratatui::crossterm; use crate::cli::folder::FolderCommand; diff --git a/src/cli/sync.rs b/src/cli/sync.rs index 115791c..f37e4ab 100755 --- a/src/cli/sync.rs +++ b/src/cli/sync.rs @@ -7,7 +7,6 @@ use futures::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; use itertools::Itertools; use tokio::time::Instant; -use crate::models; use crate::models::{DLSiteCategory, DLSiteGenre, DLSiteManiax, DLSiteTranslation}; use crate::config::types::ApplicationConfig; use crate::constants::{DB_CF_OPTIONS, DB_OPTIONS}; @@ -113,7 +112,7 @@ impl DLSiteSyncCommand { } async fn sync_works(&self, app_conf: &ApplicationConfig, db: &mut RocksDB, crawler: &DLSiteCrawler) -> Result<()> { - let existing_works = db.get_all_values::()?; + let existing_works = db.get_all_values::()?; let work_list = self.get_work_list(&app_conf, &existing_works).await?; let rj_nums = work_list.clone().into_keys().collect::>(); @@ -151,7 +150,7 @@ impl DLSiteSyncCommand { Ok(()) } - async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[models::DLSiteManiax]) -> Result> { + async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[DLSiteManiax]) -> Result> { let existing_nums = existing_works.iter() .map(|x| x.rj_num.clone()) .collect::>(); diff --git a/src/config/mod.rs b/src/config/mod.rs index dd2f4bd..5f9d51b 100755 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -17,8 +17,10 @@ pub(crate) struct GameList { impl ApplicationConfig { pub fn get_config() -> Result { - if CACHE_MAP.contains_key(CONFIG_KEY) { - Ok(serde_json::from_value(CACHE_MAP.get(CONFIG_KEY).unwrap().clone())?) + if CACHE_MAP.contains_key(CONFIG_KEY) && + let Some(cached_config) = CACHE_MAP.get(CONFIG_KEY) + { + Ok(serde_json::from_value(cached_config.clone())?) } else if APP_CONIFG_FILE_PATH.exists() { ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH) } else { @@ -37,7 +39,6 @@ impl ApplicationConfig { let default_locale = sys_locale::get_locale().unwrap_or(String::from("ja-JP")); let conf = Self { basic_config: BasicConfig { - db_path: APP_DB_DATA_DIR.to_str().unwrap().to_string(), tick_rate: 250, locale: LanguageTag::parse(&default_locale)?, }, @@ -45,18 +46,19 @@ impl ApplicationConfig { dlsite_paths: vec![], }, }; - conf.clone().write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())?; + conf.clone().write_to_file(APP_CONIFG_FILE_PATH.to_path_buf())?; Ok(conf) } - fn write_to_file(self, path: &PathBuf) -> Result<()> { + fn write_to_file(self, path: PathBuf) -> Result<()> { let writer = std::fs::File::create(path)?; serde_json::to_writer_pretty(writer, &self)?; Ok(()) - } pub fn save(self) -> Result<()> { - self.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf()) + let current_value = serde_json::to_value(&self)?; + CACHE_MAP.alter(CONFIG_KEY, |_, _| current_value); + self.write_to_file(APP_CONIFG_FILE_PATH.to_path_buf()) } } diff --git a/src/config/types.rs b/src/config/types.rs index 0562a6c..8f20552 100755 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -9,7 +9,6 @@ pub struct ApplicationConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct BasicConfig { - pub db_path: String, pub locale: LanguageTag, pub tick_rate: u64, } diff --git a/src/crawler/dlsite.rs b/src/crawler/dlsite.rs index 08fe389..3df80fa 100755 --- a/src/crawler/dlsite.rs +++ b/src/crawler/dlsite.rs @@ -12,12 +12,10 @@ use lazy_static::lazy_static; use scraper::{Element, Html, Selector}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::config::types::ApplicationConfig; use crate::constants::{APP_DATA_DIR, JP_LOCALE}; use crate::crawler::Crawler; use crate::helpers::matches_primary_language; -use crate::models; -use crate::models::{DLSiteTranslation, PrimaryLanguage}; +use crate::models::{PrimaryLanguage}; //TODO: override locale with user one const DLSITE_URL: &str = "https://www.dlsite.com/"; @@ -167,7 +165,6 @@ impl DLSiteCrawler { let result = html.select(&selector).next().unwrap(); let genre_rows = result.child_elements().collect::>(); - let t = genre_rows.iter().filter_map(|v| v.first_element_child().unwrap().text().next()).collect_vec(); let genre_row = genre_rows.iter() .find(|v| v.first_element_child().unwrap().text().next().unwrap() == genre_str) .unwrap(); diff --git a/src/crawler/mod.rs b/src/crawler/mod.rs index 49b1b64..0578470 100755 --- a/src/crawler/mod.rs +++ b/src/crawler/mod.rs @@ -1,6 +1,5 @@ pub mod dlsite; -use std::sync::Arc; pub use dlsite::*; use color_eyre::eyre::eyre; use crate::constants::APP_CACHE_PATH; diff --git a/src/event.rs b/src/event.rs index df19ee4..ff43064 100755 --- a/src/event.rs +++ b/src/event.rs @@ -3,7 +3,7 @@ use crossterm::event::EventStream; use futures::FutureExt; use futures::StreamExt; use std::time::Duration; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; #[derive(Clone)] @@ -15,7 +15,7 @@ pub(crate) enum AppEvent { pub(crate) struct EventHandler { _tx: UnboundedSender, - rx: tokio::sync::mpsc::UnboundedReceiver, + rx: UnboundedReceiver, pub task: JoinHandle<()>, } diff --git a/src/helpers/db.rs b/src/helpers/db.rs index 8282645..d96ccc9 100755 --- a/src/helpers/db.rs +++ b/src/helpers/db.rs @@ -1,11 +1,9 @@ -use std::path::Path; -use crate::constants::{DB_COLUMNS}; +use crate::constants::{APP_DB_DATA_DIR, DB_COLUMNS}; use rocksdb::{ColumnFamilyDescriptor, IteratorMode, OptimisticTransactionDB, Options}; use serde::{Serialize}; use serde::de::DeserializeOwned; use crate::models::{RocksColumn, RocksReference, RocksReferences}; use color_eyre::Result; -use crate::config::types::ApplicationConfig; pub struct RocksDB { db: OptimisticTransactionDB, @@ -13,13 +11,12 @@ pub struct RocksDB { impl RocksDB { pub fn new(db_opts: Options, cf_opts: Options) -> Result { - let app_conf = ApplicationConfig::get_config()?; let cfs = DB_COLUMNS.iter() .map(|cf| ColumnFamilyDescriptor::new(cf.to_string(), cf_opts.clone())) .collect::>(); let db = OptimisticTransactionDB::open_cf_descriptors( &db_opts, - Path::new(&app_conf.basic_config.db_path), + APP_DB_DATA_DIR.as_path(), cfs )?; let rocks = Self { diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 907132b..3821ada 100755 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -5,13 +5,11 @@ use color_eyre::eyre::eyre; use color_eyre::owo_colors::OwoColorize; use language_tags::LanguageTag; use tokio::fs; -use crate::config::types::ApplicationConfig; -use crate::constants::{APP_CONFIG_DIR, APP_DATA_DIR, SUPPORTED_LOCALES}; +use crate::constants::{APP_CONFIG_DIR, APP_DATA_DIR, APP_DB_DATA_DIR}; use crate::crawler::DLSITE_IMG_FOLDER; pub async fn initialize_folders() -> color_eyre::Result<()> { - let app_conf = ApplicationConfig::get_config()?; if !APP_CONFIG_DIR.exists() { fs::create_dir_all(APP_CONFIG_DIR.as_path()).await?; } @@ -21,9 +19,8 @@ pub async fn initialize_folders() -> color_eyre::Result<()> { if !DLSITE_IMG_FOLDER.exists() { fs::create_dir_all(DLSITE_IMG_FOLDER.as_path()).await?; } - let db_path = Path::new(&app_conf.basic_config.db_path); - if !db_path.exists() { - fs::create_dir_all(db_path).await?; + if !APP_DB_DATA_DIR.exists() { + fs::create_dir_all(APP_DB_DATA_DIR.as_path()).await?; } Ok(()) } diff --git a/src/widgets/popups/mod.rs b/src/widgets/popups/mod.rs index e9011fb..a370e97 100755 --- a/src/widgets/popups/mod.rs +++ b/src/widgets/popups/mod.rs @@ -1 +1,5 @@ pub mod folder; + +pub enum AppPopup { + AddFolder(folder::AddFolderPopup) +} diff --git a/src/widgets/views/main_view.rs b/src/widgets/views/main_view.rs index 1f0d7dd..3f1f618 100755 --- a/src/widgets/views/main_view.rs +++ b/src/widgets/views/main_view.rs @@ -2,21 +2,20 @@ use crate::config::types::ApplicationConfig; use crate::widgets::popups::folder::AddFolderPopup; use crate::widgets::views::View; use crossterm::event::KeyCode::Char; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; +use crossterm::event::{Event, KeyCode, KeyEventKind}; use rat_cursor::HasScreenCursor; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Line, Span, Style, Text, Widget}; use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget}; -use std::any::Any; +use crate::widgets::popups::AppPopup; pub struct MainView { pub state: MainViewState, } -#[derive(Debug)] pub struct MainViewState { - popup: Option>, + popup: Option, status: Status, } @@ -46,42 +45,45 @@ impl MainView { } fn folder_popup(&mut self) { - self.state.popup = Some(Box::new(AddFolderPopup::new())); + self.state.popup = Some(AppPopup::AddFolder(AddFolderPopup::new())); self.state.status = Status::Popup; } } impl View for MainView { fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()> { - if let Some(any) = self.state.popup.as_mut() - && let Some(popup) = any.downcast_mut::() - { - popup.textarea.handle_input(event)?; - } - Ok(()) - } + if let Some(current_popup) = self.state.popup.as_mut() { + match current_popup { + AppPopup::AddFolder(folder_popup) => { + folder_popup.textarea.handle_input(event)?; + if let Event::Key(key) = event && + key.code.is_enter() && + let Some(value) = folder_popup.get_folder_value() + { + let mut config = ApplicationConfig::get_config()?; + config.path_config.dlsite_paths.push(value); - fn handle_key_input(&mut self, key: &KeyEvent) -> color_eyre::Result<()> { - if matches!(self.state.status, Status::Popup) && matches!(key.code, KeyCode::Esc) { - self.state.status = Status::Running; - self.state.popup = None; + folder_popup.textarea.reset_value()?; + config.save()?; + } + } + } } - if let Some(any) = self.state.popup.as_mut() && - let Some(popup) = any.downcast_mut::() && - let Some(value) = popup.get_folder_value() && - key.code.is_enter() - { - let mut config = ApplicationConfig::get_config()?; - config.path_config.dlsite_paths.push(value); - - popup.textarea.reset_value()?; - config.save()?; - } - if !matches!(self.state.status, Status::Popup) && matches!(key.kind, KeyEventKind::Press) { - match key.code { - Char('q') => self.quit()?, - Char('a') => self.folder_popup(), - _ => {} + if let Event::Key(key_event) = event { + if matches!(self.state.status, Status::Popup) && + matches!(key_event.code, KeyCode::Esc) + { + self.state.status = Status::Running; + self.state.popup = None; + } + if !matches!(self.state.status, Status::Popup) && + matches!(key_event.kind, KeyEventKind::Press) + { + match key_event.code { + Char('q') => self.quit()?, + Char('a') => self.folder_popup(), + _ => {} + } } } Ok(()) @@ -111,22 +113,27 @@ impl StatefulWidget for MainView { Self::render_game_list(chunks[1], buf); Self::render_footer(state, chunks[2], buf); - if let Some(boxed) = state.popup.as_mut() - && let Some(popup) = boxed.downcast_mut::() - { - popup.clone().render(area, buf, popup); + let Some(popup) = state.popup.as_mut() else { + return; + }; + match popup { + AppPopup::AddFolder(popup) => { + popup.clone().render(area, buf, popup); + } } } } impl HasScreenCursor for MainView { fn screen_cursor(&self) -> Option<(u16, u16)> { - if let Some(popup) = &self.state.popup - && let Some(add_folder) = popup.downcast_ref::() - { - return add_folder.textarea.screen_cursor(); + let Some(popup) = &self.state.popup else { + return None; + }; + match popup { + AppPopup::AddFolder(popup) => { + popup.textarea.screen_cursor() + } } - None } } diff --git a/src/widgets/views/mod.rs b/src/widgets/views/mod.rs index 2640f77..6b3c8a2 100755 --- a/src/widgets/views/mod.rs +++ b/src/widgets/views/mod.rs @@ -1,10 +1,13 @@ mod main_view; -use crossterm::event::{Event, KeyEvent}; +use crossterm::event::{Event}; pub use main_view::MainView; pub trait View { fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()>; - fn handle_key_input(&mut self, key: &KeyEvent) -> color_eyre::Result<()>; fn is_running(&self) -> bool; } + +pub enum AppView { + MainView(MainView), +} \ No newline at end of file