proton_call/
index.rs

1use crate::error::{Error, Kind};
2use crate::{pass, throw, Version};
3use lliw::Fg::LightYellow as Yellow;
4use lliw::Reset;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::ffi::OsString;
8use std::fmt::{Display, Formatter};
9use std::fs::{DirEntry, File, OpenOptions};
10use std::io::{Read, Write};
11use std::path::{Path, PathBuf};
12
13/// Index type to Index Proton versions in common
14#[derive(Debug, Serialize, Deserialize)]
15pub struct Index {
16    dir: PathBuf,
17    inner: HashMap<Version, PathBuf>,
18}
19
20impl Display for Index {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        let mut str: String = format!(
23            "Indexed Directory: {}\n\nIndexed {} Proton Versions:\n",
24            self.dir.to_string_lossy(),
25            self.len()
26        );
27
28        for (version, path) in &self.inner {
29            str = format!("{}\nProton {}: {}", str, version, path.display());
30        }
31
32        write!(f, "{}", str)
33    }
34}
35
36impl Index {
37    /// Creates an index of Proton versions in given path
38    ///
39    /// # Errors
40    ///
41    /// Will fail if Indexing fails to read the directory
42    pub fn new(index: &Path) -> Result<Index, Error> {
43        let mut idx = Index {
44            dir: index.to_path_buf(),
45            inner: HashMap::new(),
46        };
47
48        idx.load()?;
49        Ok(idx)
50    }
51
52    #[must_use]
53    #[inline]
54    /// Returns the number of Indexed Protons
55    pub fn len(&self) -> usize {
56        self.inner.len()
57    }
58
59    #[must_use]
60    #[inline]
61    /// Returns true if Index is empty
62    pub fn is_empty(&self) -> bool {
63        self.inner.is_empty()
64    }
65
66    #[must_use]
67    #[inline]
68    /// Retrieves the path of the requested Proton version
69    pub fn get(&self, version: &Version) -> Option<PathBuf> {
70        self.inner.get(version).map(std::clone::Clone::clone)
71    }
72
73    fn cache_location() -> Result<PathBuf, Error> {
74        use std::env::var;
75
76        if let Ok(val) = var("HOME") {
77            let path = format!("{}/.cache/proton/index", val);
78            return Ok(PathBuf::from(path));
79        }
80
81        throw!(Kind::Environment, "$HOME does not exist")
82    }
83
84    fn open_cache() -> Result<File, Error> {
85        let path: PathBuf = Self::cache_location()?;
86
87        if let Some(parent) = path.parent() {
88            if !parent.exists() {
89                if let Err(e) = std::fs::create_dir(parent) {
90                    throw!(Kind::IndexCache, "{}", e);
91                }
92            }
93        }
94
95        match OpenOptions::new()
96            .read(true)
97            .write(true)
98            .create(true)
99            .open(path)
100        {
101            Ok(cache) => pass!(cache),
102            Err(e) => throw!(Kind::IndexCache, "{}", e),
103        }
104    }
105
106    fn load(&mut self) -> Result<(), Error> {
107        if let Err(e) = self._load() {
108            eprintln!("{}warning{}: {}\nreindexing...", Yellow, Reset, e);
109            self.index()?;
110
111            if let Err(e) = self.save() {
112                eprintln!("{}warning:{} {}\n", Yellow, Reset, e);
113            }
114        }
115
116        Ok(())
117    }
118
119    fn _load(&mut self) -> Result<(), Error> {
120        let cache: File = Self::open_cache()?;
121        self.read_index(cache)?;
122        Ok(())
123    }
124
125    fn read_index(&mut self, mut f: File) -> Result<(), Error> {
126        let mut buf: Vec<u8> = Vec::new();
127
128        if let Err(e) = f.read_to_end(&mut buf) {
129            throw!(Kind::IndexCache, "{}", e);
130        }
131
132        self.inner = match bincode::deserialize::<Self>(&buf) {
133            Ok(c) => c.inner,
134            Err(_) => throw!(Kind::IndexCache, "can't deserialize"),
135        };
136
137        Ok(())
138    }
139
140    fn save(&self) -> Result<(), Error> {
141        let mut cache: File = Self::open_cache()?;
142
143        let bytes: Vec<u8> = match bincode::serialize(self) {
144            Ok(b) => b,
145            Err(e) => throw!(Kind::IndexCache, "{}", e),
146        };
147
148        if let Err(e) = cache.write(&bytes) {
149            throw!(Kind::IndexCache, "{}", e);
150        }
151
152        Ok(())
153    }
154
155    // TODO: Refactor and optimize `index` method.
156
157    /// Indexes Proton versions
158    /// # Errors
159    /// An error is returned when the function cannot read the common directory
160    pub fn index(&mut self) -> Result<(), Error> {
161        if let Ok(rd) = self.dir.read_dir() {
162            for result_entry in rd {
163                let entry: DirEntry = if let Ok(e) = result_entry {
164                    e
165                } else {
166                    eprintln!("{}warning:{} failed indexing a directory...", Yellow, Reset);
167                    continue;
168                };
169
170                let entry_path: PathBuf = entry.path();
171
172                if entry_path.is_dir() {
173                    let name: OsString = entry.file_name();
174                    let name: String = name.to_string_lossy().to_string();
175                    if let Some(version_str) = name.split(' ').last() {
176                        if let Ok(version) = version_str.parse() {
177                            self.inner.insert(version, entry_path);
178                        }
179                    }
180                }
181            }
182        } else {
183            throw!(Kind::IndexReadDir, "can not read common dir");
184        }
185
186        pass!()
187    }
188}