Reorganize state module
This commit is contained in:
parent
8c5be0c371
commit
6f0ab738f0
@ -1,19 +1,27 @@
|
|||||||
mod edit;
|
mod edit;
|
||||||
mod show;
|
mod show;
|
||||||
mod state;
|
|
||||||
mod tui;
|
mod tui;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
|
||||||
|
|
||||||
|
use std::fs::create_dir_all;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser as ArgParser;
|
use clap::Parser as ArgParser;
|
||||||
|
use directories::ProjectDirs;
|
||||||
use tracing::Level as TracingLevel;
|
use tracing::Level as TracingLevel;
|
||||||
use tracing_appender::rolling;
|
use tracing_appender::rolling;
|
||||||
use tracing_subscriber::filter::Targets;
|
use tracing_subscriber::filter::Targets;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
use crate::state::logs_dir;
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref DIRS: ProjectDirs = {
|
||||||
|
ProjectDirs::from("", "valeth", "wayfarer").unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, ArgParser)]
|
#[derive(Debug, ArgParser)]
|
||||||
@ -80,3 +88,17 @@ fn tracing_setup() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn logs_dir() -> Result<PathBuf> {
|
||||||
|
let log_root_path = DIRS
|
||||||
|
.state_dir()
|
||||||
|
.unwrap_or_else(|| DIRS.cache_dir())
|
||||||
|
.join("logs");
|
||||||
|
|
||||||
|
if !log_root_path.exists() {
|
||||||
|
create_dir_all(&log_root_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(log_root_path)
|
||||||
|
}
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
use std::fs::create_dir_all;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use std::{
|
|
||||||
fs::{self, read_to_string},
|
|
||||||
io::Write,
|
|
||||||
os::unix::prelude::OsStrExt,
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use directories::ProjectDirs;
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use jrny_save::Savefile;
|
|
||||||
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref DIRS: ProjectDirs = {
|
|
||||||
ProjectDirs::from("", "valeth", "wayfarer").unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn logs_dir() -> Result<PathBuf> {
|
|
||||||
let log_root_path = DIRS
|
|
||||||
.state_dir()
|
|
||||||
.unwrap_or_else(|| DIRS.cache_dir())
|
|
||||||
.join("logs");
|
|
||||||
|
|
||||||
if !log_root_path.exists() {
|
|
||||||
create_dir_all(&log_root_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(log_root_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct PersistentState {
|
|
||||||
pub savefile: Option<Savefile>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
impl PersistentState {
|
|
||||||
pub fn load() -> Result<Self> {
|
|
||||||
let data_dir = DIRS.data_local_dir();
|
|
||||||
|
|
||||||
if !data_dir.exists() {
|
|
||||||
create_dir_all(&data_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let savefile = load_last_active_savefile()?;
|
|
||||||
|
|
||||||
Ok(Self { savefile })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
|
||||||
pub fn reload_active_savefile(&mut self) -> Result<()> {
|
|
||||||
if let Some(cur_savefile) = &self.savefile {
|
|
||||||
let new_savefile = Savefile::from_path(&cur_savefile.path)?;
|
|
||||||
self.savefile = Some(new_savefile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_savefile_path<P>(&mut self, path: P) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let savefile = Savefile::from_path(&path)?;
|
|
||||||
|
|
||||||
let state_path = DIRS.data_local_dir().join("active_savefile");
|
|
||||||
let mut state_file = fs::OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.truncate(true)
|
|
||||||
.create(true)
|
|
||||||
.open(state_path)?;
|
|
||||||
|
|
||||||
let active_savefile = savefile.path.as_os_str().as_bytes();
|
|
||||||
|
|
||||||
state_file.write_all(active_savefile)?;
|
|
||||||
self.savefile = Some(savefile);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn load_last_active_savefile() -> Result<Option<Savefile>> {
|
|
||||||
let state_path = DIRS.data_local_dir().join("active_savefile");
|
|
||||||
|
|
||||||
if !state_path.exists() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = read_to_string(&state_path).unwrap();
|
|
||||||
|
|
||||||
let savefile = Savefile::from_path(path.trim_end())?;
|
|
||||||
|
|
||||||
Ok(Some(savefile))
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
#![cfg(feature = "tui")]
|
#![cfg(feature = "tui")]
|
||||||
|
|
||||||
mod events;
|
mod events;
|
||||||
|
mod state;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
|
||||||
@ -16,11 +17,8 @@ use crossterm::terminal::{
|
|||||||
};
|
};
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::backend::CrosstermBackend;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
use tui_input::Input;
|
|
||||||
|
|
||||||
use crate::state::PersistentState;
|
use self::state::{Mode, State};
|
||||||
#[cfg(feature = "watch")]
|
|
||||||
use crate::watcher::FileWatcher;
|
|
||||||
use crate::Args as AppArgs;
|
use crate::Args as AppArgs;
|
||||||
|
|
||||||
|
|
||||||
@ -31,15 +29,6 @@ type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
|||||||
pub struct Args;
|
pub struct Args;
|
||||||
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
persistent: PersistentState,
|
|
||||||
mode: Mode,
|
|
||||||
file_select: Input,
|
|
||||||
#[cfg(feature = "watch")]
|
|
||||||
file_watcher: Option<FileWatcher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
@ -57,30 +46,11 @@ pub enum Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Mode {
|
|
||||||
#[default]
|
|
||||||
Normal,
|
|
||||||
|
|
||||||
ShowError(String),
|
|
||||||
|
|
||||||
SelectFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn execute(_app_args: &AppArgs, _args: &Args) -> Result<()> {
|
pub(crate) fn execute(_app_args: &AppArgs, _args: &Args) -> Result<()> {
|
||||||
let persistent = PersistentState::load()?;
|
let state = State::load()?;
|
||||||
|
|
||||||
let mut terminal = setup()?;
|
let mut terminal = setup()?;
|
||||||
|
|
||||||
let state = State {
|
|
||||||
persistent,
|
|
||||||
mode: Mode::default(),
|
|
||||||
file_select: Input::default(),
|
|
||||||
#[cfg(feature = "watch")]
|
|
||||||
file_watcher: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
run(&mut terminal, state)?;
|
run(&mut terminal, state)?;
|
||||||
|
|
||||||
reset(terminal)?;
|
reset(terminal)?;
|
||||||
@ -137,45 +107,39 @@ fn handle_message(
|
|||||||
|
|
||||||
Message::LoadFile => {
|
Message::LoadFile => {
|
||||||
let file_path = state.file_select.value();
|
let file_path = state.file_select.value();
|
||||||
|
|
||||||
info!("Loading file {}", file_path);
|
info!("Loading file {}", file_path);
|
||||||
|
|
||||||
state.persistent.set_active_savefile_path(file_path)?;
|
state.set_selected_as_active_savefile()?;
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
if state.file_watcher.is_some() {
|
if state.is_watching_file() {
|
||||||
state.file_watcher = None;
|
state.reset_file_watcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_tx.send(Message::SetMode(Mode::Normal))?;
|
msg_tx.send(Message::SetMode(Mode::Normal))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
Message::ToggleFileWatch if state.persistent.savefile.is_some() => {
|
Message::ToggleFileWatch => {
|
||||||
let savefile = state.persistent.savefile.as_ref().unwrap();
|
if let Some(savefile) = state.savefile() {
|
||||||
|
if state.is_watching_file() {
|
||||||
|
let evq_tx = msg_tx.clone();
|
||||||
|
let callback = move || {
|
||||||
|
evq_tx.send(Message::ReloadFile).unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
if state.file_watcher.is_none() {
|
info!("Starting file watcher on {}", savefile.path.display());
|
||||||
let evq_tx = msg_tx.clone();
|
state.enable_file_watcher(callback);
|
||||||
let callback = move || {
|
} else {
|
||||||
evq_tx.send(Message::ReloadFile).unwrap();
|
info!("Stopped file watcher on {}", savefile.path.display());
|
||||||
};
|
state.reset_file_watcher();
|
||||||
|
}
|
||||||
info!("Starting file watcher on {}", savefile.path.display());
|
|
||||||
|
|
||||||
let file_watcher = FileWatcher::new(&savefile.path, callback);
|
|
||||||
state.file_watcher = Some(file_watcher);
|
|
||||||
} else {
|
|
||||||
info!("Stopped file watcher on {}", savefile.path.display());
|
|
||||||
|
|
||||||
state.file_watcher = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
Message::ReloadFile if state.persistent.savefile.is_some() => {
|
Message::ReloadFile => {
|
||||||
debug!("Reloading file");
|
state.reload_active_savefile()?;
|
||||||
|
|
||||||
state.persistent.reload_active_savefile()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
|
122
crates/wayfarer/src/tui/state.rs
Normal file
122
crates/wayfarer/src/tui/state.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
use std::fs::{self, create_dir_all, read_to_string};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::prelude::OsStrExt;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use jrny_save::Savefile;
|
||||||
|
use tui_input::Input;
|
||||||
|
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
use crate::watcher::FileWatcher;
|
||||||
|
use crate::DIRS;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
savefile: Option<Savefile>,
|
||||||
|
pub mode: Mode,
|
||||||
|
pub file_select: Input,
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
file_watcher: Option<FileWatcher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Mode {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
ShowError(String),
|
||||||
|
|
||||||
|
SelectFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn load() -> Result<Self> {
|
||||||
|
let data_dir = DIRS.data_local_dir();
|
||||||
|
|
||||||
|
if !data_dir.exists() {
|
||||||
|
create_dir_all(&data_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let savefile = load_last_active_savefile()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
savefile,
|
||||||
|
mode: Mode::default(),
|
||||||
|
file_select: Input::default(),
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
file_watcher: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn savefile(&self) -> Option<&Savefile> {
|
||||||
|
self.savefile.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selected_as_active_savefile(&mut self) -> Result<()> {
|
||||||
|
let savefile = Savefile::from_path(&self.file_select.value())?;
|
||||||
|
|
||||||
|
let state_path = DIRS.data_local_dir().join("active_savefile");
|
||||||
|
let mut state_file = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.create(true)
|
||||||
|
.open(state_path)?;
|
||||||
|
|
||||||
|
let active_savefile = savefile.path.as_os_str().as_bytes();
|
||||||
|
|
||||||
|
state_file.write_all(active_savefile)?;
|
||||||
|
self.savefile = Some(savefile);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
pub fn is_watching_file(&self) -> bool {
|
||||||
|
self.file_watcher.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
pub fn enable_file_watcher<F>(&mut self, callback: F)
|
||||||
|
where
|
||||||
|
F: Fn() + Send + 'static,
|
||||||
|
{
|
||||||
|
if let Some(savefile) = &self.savefile {
|
||||||
|
let file_watcher = FileWatcher::new(&savefile.path, callback);
|
||||||
|
self.file_watcher = Some(file_watcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
pub fn reset_file_watcher(&mut self) {
|
||||||
|
self.file_watcher = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "watch")]
|
||||||
|
pub fn reload_active_savefile(&mut self) -> Result<()> {
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
if let Some(cur_savefile) = &self.savefile {
|
||||||
|
debug!("Reloading file");
|
||||||
|
let new_savefile = Savefile::from_path(&cur_savefile.path)?;
|
||||||
|
self.savefile = Some(new_savefile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load_last_active_savefile() -> Result<Option<Savefile>> {
|
||||||
|
let state_path = DIRS.data_local_dir().join("active_savefile");
|
||||||
|
|
||||||
|
if !state_path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = read_to_string(&state_path)?;
|
||||||
|
|
||||||
|
let savefile = Savefile::from_path(path.trim_end())?;
|
||||||
|
|
||||||
|
Ok(Some(savefile))
|
||||||
|
}
|
@ -7,7 +7,7 @@ use crate::tui::State;
|
|||||||
|
|
||||||
|
|
||||||
pub fn render(state: &mut State, frame: &mut Frame, area: Rect) {
|
pub fn render(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||||
match &state.persistent.savefile {
|
match state.savefile() {
|
||||||
Some(savefile) => render_info(savefile, frame, area),
|
Some(savefile) => render_info(savefile, frame, area),
|
||||||
None => render_no_active_file(frame, area),
|
None => render_no_active_file(frame, area),
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,20 @@ pub fn render(state: &State, mut frame: &mut Frame, area: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
Mode::Normal if state.file_watcher.is_some() && state.persistent.savefile.is_some() => {
|
Mode::Normal if state.is_watching_file() => {
|
||||||
let savefile = state.persistent.savefile.as_ref().unwrap();
|
if let Some(savefile) = state.savefile() {
|
||||||
let text = format!("Watching file: {}", savefile.path.display());
|
let text = format!("Watching file: {}", savefile.path.display());
|
||||||
let status = Paragraph::new(text).block(status_block);
|
let status = Paragraph::new(text).block(status_block);
|
||||||
frame.render_widget(status, area);
|
frame.render_widget(status, area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mode::Normal if state.persistent.savefile.is_none() => (),
|
|
||||||
|
|
||||||
Mode::Normal => {
|
Mode::Normal => {
|
||||||
let savefile = state.persistent.savefile.as_ref().unwrap();
|
if let Some(savefile) = state.savefile() {
|
||||||
let text = format!("Showing file: {}", savefile.path.display());
|
let text = format!("Showing file: {}", savefile.path.display());
|
||||||
let status = Paragraph::new(text).block(status_block);
|
let status = Paragraph::new(text).block(status_block);
|
||||||
frame.render_widget(status, area);
|
frame.render_widget(status, area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mode::SelectFile => render_file_select(&state, &mut frame, status_block, area),
|
Mode::SelectFile => render_file_select(&state, &mut frame, status_block, area),
|
||||||
|
Loading…
Reference in New Issue
Block a user