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#[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 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 pub fn len(&self) -> usize {
56 self.inner.len()
57 }
58
59 #[must_use]
60 #[inline]
61 pub fn is_empty(&self) -> bool {
63 self.inner.is_empty()
64 }
65
66 #[must_use]
67 #[inline]
68 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 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}