thedes_app/root/
load_game.rs1use std::{
2 fmt,
3 path::{Path, PathBuf},
4};
5
6use thedes_tui::{
7 cancellability::Cancellable,
8 core::App,
9 menu::{self, Menu},
10};
11use thiserror::Error;
12use tokio::{fs, io};
13
14use crate::SAVE_EXTENSION;
15
16#[derive(Debug, Error)]
17pub enum Error {
18 #[error("Failed to read saves from directory {}", path.display())]
19 ReadDir {
20 path: PathBuf,
21 #[source]
22 source: io::Error,
23 },
24 #[error("Failed to initialize menu")]
25 InitMenu(#[source] menu::Error),
26 #[error("Failed to run menu")]
27 RunMenu(#[source] menu::Error),
28}
29
30#[derive(Debug, Clone)]
31struct SaveItem {
32 name: String,
33 path: PathBuf,
34}
35
36impl fmt::Display for SaveItem {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "{}", self.name)
39 }
40}
41
42#[derive(Debug, Clone)]
43pub struct Component {
44 _private: (),
45}
46
47impl Component {
48 pub fn new() -> Self {
49 Self { _private: () }
50 }
51
52 pub async fn run(
53 &self,
54 saves_dir: impl AsRef<Path>,
55 app: &mut App,
56 ) -> Result<Option<PathBuf>, Error> {
57 let saves = self.collect_saves(saves_dir.as_ref()).await?;
58 let mut menu = Menu::from_cancellation(
59 "⇑⇑ Load Game ⇑⇑",
60 saves,
61 Cancellable::new(false),
62 )
63 .map_err(Error::InitMenu)?;
64
65 menu.run(app).await.map_err(Error::RunMenu)?;
66
67 let output = menu.output().map(|item| item.path.clone());
68 Ok(output)
69 }
70
71 async fn collect_saves(
72 &self,
73 saves_dir: &Path,
74 ) -> Result<Vec<SaveItem>, Error> {
75 let mut dir = fs::read_dir(saves_dir).await.map_err(|source| {
76 Error::ReadDir { path: saves_dir.to_owned(), source }
77 })?;
78
79 let mut items = Vec::<SaveItem>::new();
80 while let Some(entry) = dir.next_entry().await.map_err(|source| {
81 Error::ReadDir { path: saves_dir.to_owned(), source }
82 })? {
83 let Some(name) = entry
84 .file_name()
85 .to_string_lossy()
86 .strip_suffix(SAVE_EXTENSION)
87 .map(str::to_owned)
88 else {
89 continue;
90 };
91 let path = entry.path().to_owned();
92 items.push(SaveItem { name, path });
93 }
94
95 Ok(items)
96 }
97}