Refactor code to remove Any
This commit is contained in:
@@ -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"] }
|
||||
|
||||
|
||||
50
src/app.rs
50
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<Box<dyn Any>>,
|
||||
view: Option<AppView>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub async fn create() -> Result<Self> {
|
||||
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::<MainView>()
|
||||
&& 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::<MainView>() {
|
||||
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::<MainView>() {
|
||||
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::<MainView>() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::<models::DLSiteManiax>()?;
|
||||
let existing_works = db.get_all_values::<DLSiteManiax>()?;
|
||||
|
||||
let work_list = self.get_work_list(&app_conf, &existing_works).await?;
|
||||
let rj_nums = work_list.clone().into_keys().collect::<Vec<_>>();
|
||||
@@ -151,7 +150,7 @@ impl DLSiteSyncCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[models::DLSiteManiax]) -> Result<HashMap<String, PathBuf>> {
|
||||
async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[DLSiteManiax]) -> Result<HashMap<String, PathBuf>> {
|
||||
let existing_nums = existing_works.iter()
|
||||
.map(|x| x.rj_num.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -17,8 +17,10 @@ pub(crate) struct GameList<T> {
|
||||
|
||||
impl ApplicationConfig {
|
||||
pub fn get_config() -> Result<Self> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<AppEvent>,
|
||||
rx: tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
|
||||
rx: UnboundedReceiver<AppEvent>,
|
||||
pub task: JoinHandle<()>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Self> {
|
||||
let app_conf = ApplicationConfig::get_config()?;
|
||||
let cfs = DB_COLUMNS.iter()
|
||||
.map(|cf| ColumnFamilyDescriptor::new(cf.to_string(), cf_opts.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
pub mod folder;
|
||||
|
||||
pub enum AppPopup {
|
||||
AddFolder(folder::AddFolderPopup)
|
||||
}
|
||||
|
||||
@@ -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<Box<dyn Any>>,
|
||||
popup: Option<AppPopup>,
|
||||
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::<AddFolderPopup>()
|
||||
{
|
||||
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::<AddFolderPopup>() &&
|
||||
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::<AddFolderPopup>()
|
||||
{
|
||||
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::<AddFolderPopup>()
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
Reference in New Issue
Block a user