1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
use std::{collections::{BTreeMap, HashMap}, error::Error, fmt::Write, io::ErrorKind};
use std::path::{Path, PathBuf};
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use log::{debug, warn};
use super::version::{*, manifest::*};
mod constants;
use constants::*;
#[derive(Serialize, Deserialize, Debug)]
struct RemoteVersionIndexEntry {
last_update: DateTime<Utc>
}
#[derive(Serialize, Deserialize, Debug)]
struct RemoteVersionIndex {
versions: BTreeMap<String, RemoteVersionIndexEntry>
}
impl Default for RemoteVersionIndex {
fn default() -> Self {
RemoteVersionIndex {
versions: BTreeMap::new()
}
}
}
struct RemoteVersionList {
versions: HashMap<String, VersionManifestVersion>,
index: RemoteVersionIndex
}
impl RemoteVersionList {
async fn new(home: impl AsRef<Path>) -> Result<RemoteVersionList, Box<dyn Error>> {
let text = reqwest::get(URL_VERSION_MANIFEST).await?.error_for_status()?.text().await?;
let manifest: VersionManifest = serde_json::from_str(text.as_str())?;
let mut fname = home.as_ref().to_path_buf();
fname.push(".version.json");
let index: RemoteVersionIndex = match tokio::fs::read_to_string(fname).await {
Ok(s) => serde_json::from_str(s.as_str())?,
Err(e) => {
if e.kind() == ErrorKind::NotFound {
RemoteVersionIndex::default()
} else {
return Err(Box::new(e));
}
}
};
let mut versions = HashMap::new();
for v in manifest.versions {
versions.insert(v.id.clone(), v);
}
Ok(RemoteVersionList {
versions,
index
})
}
}
struct LocalVersionList {
versions: BTreeMap<String, CompleteVersion>
}
impl LocalVersionList {
async fn load_version(path: &PathBuf) -> Result<CompleteVersion, Box<dyn Error>> {
serde_json::from_str(tokio::fs::read_to_string(path).await?)?;
}
async fn load_versions(home: impl AsRef<Path>, skip: impl Fn(&str) -> bool) -> Result<LocalVersionList, Box<dyn Error>> {
let mut rd = tokio::fs::read_dir(home).await?;
let versions = BTreeMap::new();
while let Some(ent) = rd.next_entry().await? {
if !ent.file_type().await?.is_dir() { continue; }
// when the code is fugly
let path = match ent.file_name().to_str() {
Some(s) => {
if skip(s) {
debug!("Skipping local version {s} because (I assume) it is remotely tracked.");
continue
}
/* FIXME: once https://github.com/rust-lang/rust/issues/127292 is closed,
* use add_extension to avoid extra heap allocations (they hurt my feelings) */
let mut path = ent.path();
// can't use set_extension since s might contain a . (like 1.8.9)
path.push(format!("{s}.json"));
},
/* We just ignore directories with names that contain invalid unicode. Unfortunately, the laucher
* will not be supporting such custom versions. Name your version something sensible please. */
None => {
warn!("Ignoring a local version because its id contains invalid unicode.");
continue
}
};
match Self::load_version(path).await {
Ok(v) => {
versions.insert(v.id.clone(), v);
},
Err(e) => {
// FIXME: just display the filename without to_string_lossy when https://github.com/rust-lang/rust/issues/120048 is closed
warn!("Ignoring local version {}: {e}", ent.file_name().to_string_lossy());
}
}
}
Ok(LocalVersionList { versions })
}
}
struct VersionList {
offline: bool,
remote: Option<RemoteVersionList>,
local: LocalVersionList
}
impl VersionList {
pub async fn online(home: impl AsRef<Path>) -> Result<VersionList, Box<dyn Error>> {
let remote = RemoteVersionList::new(home).await?;
let local = LocalVersionList::load_versions(home, |s| remote.versions.contains_key(s)).await?;
Ok(VersionList {
offline: false,
remote: Some(remote),
local
})
}
pub async fn offline(home: impl AsRef<Path>) -> Result<VersionList, Box<dyn Error>> {
let local = LocalVersionList::load_versions(home, |_| false).await?;
Ok(VersionList {
offline: true,
remote: None,
local
})
}
}
|