Skip to main content

thedes_app/root/
load_game.rs

1use 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}