1use crate::{app_info, built_info, VERSION};
2use anyhow::Error;
3use chrono::{DateTime, Duration, Utc};
4use derive_more::{Deref, DerefMut};
5use semver::Version;
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use tokio::fs;
9use tokio::fs::File;
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct GlobalConfig {
13 pub system: String,
14}
15
16impl Default for GlobalConfig {
17 fn default() -> Self {
18 Self {
19 system: built_info::CFG_OS.into(),
20 }
21 }
22}
23
24#[derive(Debug, Deserialize, Serialize)]
25pub struct LauncherConfig {
26 pub global: GlobalConfig,
27 pub launcher: AppState,
28 pub ri_learn: AppState,
29 pub ri_stack: AppState,
30}
31
32impl Default for LauncherConfig {
33 fn default() -> Self {
34 Self {
35 global: GlobalConfig::default(),
36 launcher: AppState {
37 version: Some(VERSION.clone()),
38 last_check: None,
39 },
40 ri_learn: AppState {
41 version: None,
42 last_check: None,
43 },
44 ri_stack: AppState {
45 version: None,
46 last_check: None,
47 },
48 }
49 }
50}
51
52#[derive(Debug, Deserialize, Serialize)]
53pub struct AppState {
54 pub version: Option<Version>,
55 pub last_check: Option<DateTime<Utc>>,
56}
57
58impl AppState {
59 pub fn is_update_required(&self) -> bool {
60 self.is_not_exist() || self.is_allowed_to_check()
61 }
62
63 pub fn is_not_exist(&self) -> bool {
64 self.version.is_none()
65 }
66
67 pub fn is_allowed_to_check(&self) -> bool {
68 let deadline = Utc::now() - Duration::days(1);
69 match &self.last_check {
70 None => true,
71 Some(last) if last <= &deadline => true,
72 Some(_recent) => false,
73 }
74 }
75
76 pub fn reset(&mut self) {
77 self.version.take();
78 self.last_check.take();
79 }
80
81 pub fn update_check(&mut self) {
82 self.last_check = Some(Utc::now());
83 }
84
85 pub fn is_outdated(&self, recent_version: Version) -> bool {
86 if let Some(current_version) = self.version.as_ref() {
87 current_version < &recent_version
88 } else {
89 true
90 }
91 }
92
93 pub fn get_version(&self) -> Result<Version, Error> {
94 self.version
95 .clone()
96 .ok_or_else(|| Error::msg("Version is not available"))
97 }
98}
99
100#[derive(Debug, Deref, DerefMut)]
101pub struct Cacher {
102 cache_dir: PathBuf,
103 bin_dir: PathBuf,
104 state_path: PathBuf,
105 #[deref]
106 #[deref_mut]
107 state: LauncherConfig,
108}
109
110impl Cacher {
111 pub async fn create() -> Result<Self, Error> {
112 let mut cache_dir =
114 dirs::cache_dir().ok_or_else(|| Error::msg("Cache directory is not available."))?;
115 cache_dir.push("rustinsight");
116
117 let mut bin_dir = cache_dir.clone();
118 bin_dir.push("bin");
119
120 let mut state_path = cache_dir.clone();
121 state_path.push("launcher.toml");
122
123 let state = LauncherConfig::default();
124 Ok(Self {
125 cache_dir,
126 bin_dir,
127 state_path,
128 state,
129 })
130 }
131
132 pub async fn initialize(&mut self) -> Result<(), Error> {
133 self.create_dirs().await?;
134 if let Err(_err) = self.try_read_state().await {
135 }
137 self.repair_config().await?; self.write_state().await?;
139 Ok(())
140 }
141
142 async fn create_dirs(&mut self) -> Result<(), Error> {
143 fs::create_dir_all(&self.bin_dir).await?;
145 Ok(())
146 }
147
148 async fn repair_config(&mut self) -> Result<(), Error> {
150 self.launcher.version = Some(VERSION.clone());
152 let mut entries = fs::read_dir(&self.bin_dir).await?;
154 let app_prefix = &app_info::LEARN.name;
155 while let Some(entry) = entries.next_entry().await? {
156 if entry.file_name().to_string_lossy().starts_with(app_prefix) {
157 return Ok(());
158 }
159 }
160 self.state.ri_learn.reset();
162 Ok(())
163 }
164
165 pub fn bin_dir(&self) -> &PathBuf {
166 &self.bin_dir
167 }
168
169 pub async fn fix_binaries(&mut self) -> Result<(), Error> {
171 let mut entries = fs::read_dir(&self.bin_dir).await?;
172 while let Some(entry) = entries.next_entry().await? {
173 let bin_file = File::open(entry.path()).await?;
174 #[cfg(not(target_os = "windows"))]
176 {
177 use std::os::unix::fs::PermissionsExt;
178 let mut perm = bin_file.metadata().await?.permissions();
179 let new_mode = perm.mode() | 0o511; perm.set_mode(new_mode);
181 bin_file.set_permissions(perm).await?;
182 }
183 #[cfg(target_os = "windows")]
184 {
185 let original_path = entry.path();
186 if original_path.extension().is_none() {
187 let path_with_ext = original_path.with_extension("exe");
188 fs::rename(original_path, path_with_ext).await?;
189 }
190 }
191 }
192 Ok(())
193 }
194
195 async fn try_read_state(&mut self) -> Result<(), Error> {
196 let contents = fs::read_to_string(&self.state_path).await?;
197 let state = toml::from_str(&contents)?;
198 self.state = state;
199 Ok(())
200 }
201
202 pub async fn write_state(&mut self) -> Result<(), Error> {
203 let contents = toml::to_string(&self.state)?;
204 fs::write(&self.state_path, contents).await?;
205 Ok(())
206 }
207
208 pub async fn remove_cache(self) -> Result<(), Error> {
209 fs::remove_dir_all(self.cache_dir).await?;
210 Ok(())
211 }
212}