1use std::{
10 borrow::Borrow,
11 cmp,
12 collections::{btree_set, BTreeSet},
13 fs,
14 io::{self, Seek, Write},
15 iter::{IntoIterator, Iterator},
16 path::{Path, PathBuf},
17};
18
19use compact_str::CompactString;
20use fs_lock::FileLock;
21use home::cargo_home;
22use miette::Diagnostic;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25
26use crate::{crate_info::CrateInfo, helpers::create_if_not_exist};
27
28const BUFFER_SIZE: usize = 4096 * 5;
30
31#[derive(Debug, Diagnostic, Error)]
32#[non_exhaustive]
33pub enum Error {
34 #[error("I/O Error: {0}")]
35 Io(#[from] io::Error),
36
37 #[error("Failed to parse json: {0}")]
38 SerdeJsonParse(#[from] serde_json::Error),
39}
40
41pub fn append_to_path<Iter, T>(path: impl AsRef<Path>, iter: Iter) -> Result<(), Error>
42where
43 Iter: IntoIterator<Item = T>,
44 Data: From<T>,
45{
46 let mut file = FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?;
47 file.seek(io::SeekFrom::End(0))?;
49
50 write_to(&mut file, &mut iter.into_iter().map(Data::from))
51}
52
53pub fn append<Iter, T>(iter: Iter) -> Result<(), Error>
54where
55 Iter: IntoIterator<Item = T>,
56 Data: From<T>,
57{
58 append_to_path(default_path()?, iter)
59}
60
61pub fn write_to(file: &mut FileLock, iter: &mut dyn Iterator<Item = Data>) -> Result<(), Error> {
62 let writer = io::BufWriter::with_capacity(BUFFER_SIZE, file);
63
64 let mut ser = serde_json::Serializer::new(writer);
65
66 for item in iter {
67 item.serialize(&mut ser)?;
68 }
69
70 ser.into_inner().flush()?;
71
72 Ok(())
73}
74
75pub fn default_path() -> Result<PathBuf, Error> {
76 let dir = cargo_home()?.join("binstall");
77
78 fs::create_dir_all(&dir)?;
79
80 Ok(dir.join("crates-v1.json"))
81}
82
83#[derive(Debug, Deserialize, Serialize)]
84pub struct Data {
85 #[serde(flatten)]
86 pub crate_info: CrateInfo,
87
88 #[serde(flatten, with = "tuple_vec_map")]
93 pub other: Vec<(CompactString, serde_json::Value)>,
94}
95
96impl From<CrateInfo> for Data {
97 fn from(crate_info: CrateInfo) -> Self {
98 Self {
99 crate_info,
100 other: Vec::new(),
101 }
102 }
103}
104
105impl From<Data> for CrateInfo {
106 fn from(data: Data) -> Self {
107 data.crate_info
108 }
109}
110
111impl Borrow<str> for Data {
112 fn borrow(&self) -> &str {
113 &self.crate_info.name
114 }
115}
116
117impl PartialEq for Data {
118 fn eq(&self, other: &Self) -> bool {
119 self.crate_info.name == other.crate_info.name
120 }
121}
122impl PartialEq<CrateInfo> for Data {
123 fn eq(&self, other: &CrateInfo) -> bool {
124 self.crate_info.name == other.name
125 }
126}
127impl PartialEq<Data> for CrateInfo {
128 fn eq(&self, other: &Data) -> bool {
129 self.name == other.crate_info.name
130 }
131}
132impl Eq for Data {}
133
134impl PartialOrd for Data {
135 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
136 Some(self.cmp(other))
137 }
138}
139
140impl Ord for Data {
141 fn cmp(&self, other: &Self) -> cmp::Ordering {
142 self.crate_info.name.cmp(&other.crate_info.name)
143 }
144}
145
146#[derive(Debug)]
147pub struct Records {
148 file: FileLock,
149 data: BTreeSet<Data>,
151}
152
153impl Records {
154 fn load_impl(&mut self) -> Result<(), Error> {
155 let reader = io::BufReader::with_capacity(BUFFER_SIZE, &mut self.file);
156 let stream_deser = serde_json::Deserializer::from_reader(reader).into_iter();
157
158 for res in stream_deser {
159 let item = res?;
160
161 self.data.replace(item);
162 }
163
164 Ok(())
165 }
166
167 pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
168 let mut this = Self {
169 file: FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?,
170 data: BTreeSet::default(),
171 };
172 this.load_impl()?;
173 Ok(this)
174 }
175
176 pub fn load() -> Result<Self, Error> {
177 Self::load_from_path(default_path()?)
178 }
179
180 pub fn overwrite(mut self) -> Result<(), Error> {
182 self.file.rewind()?;
183 write_to(&mut self.file, &mut self.data.into_iter())?;
184
185 let len = self.file.stream_position()?;
186 self.file.set_len(len)?;
187
188 Ok(())
189 }
190
191 pub fn get(&self, value: impl AsRef<str>) -> Option<&CrateInfo> {
192 self.data.get(value.as_ref()).map(|data| &data.crate_info)
193 }
194
195 pub fn contains(&self, value: impl AsRef<str>) -> bool {
196 self.data.contains(value.as_ref())
197 }
198
199 pub fn insert(&mut self, value: CrateInfo) -> bool {
204 self.data.insert(Data::from(value))
205 }
206
207 pub fn replace(&mut self, value: CrateInfo) -> Option<CrateInfo> {
209 self.data.replace(Data::from(value)).map(CrateInfo::from)
210 }
211
212 pub fn remove(&mut self, value: impl AsRef<str>) -> bool {
213 self.data.remove(value.as_ref())
214 }
215
216 pub fn take(&mut self, value: impl AsRef<str>) -> Option<CrateInfo> {
217 self.data.take(value.as_ref()).map(CrateInfo::from)
218 }
219
220 pub fn len(&self) -> usize {
221 self.data.len()
222 }
223
224 pub fn is_empty(&self) -> bool {
225 self.data.is_empty()
226 }
227}
228
229impl<'a> IntoIterator for &'a Records {
230 type Item = &'a Data;
231
232 type IntoIter = btree_set::Iter<'a, Data>;
233
234 fn into_iter(self) -> Self::IntoIter {
235 self.data.iter()
236 }
237}
238
239#[cfg(test)]
240mod test {
241 use super::*;
242 use crate::crate_info::CrateSource;
243
244 use compact_str::CompactString;
245 use detect_targets::TARGET;
246 use semver::Version;
247 use tempfile::NamedTempFile;
248
249 macro_rules! assert_records_eq {
250 ($records:expr, $metadata_set:expr) => {
251 assert_eq!($records.len(), $metadata_set.len());
252 for (record, metadata) in $records.into_iter().zip($metadata_set.iter()) {
253 assert_eq!(record, metadata);
254 }
255 };
256 }
257
258 #[test]
259 fn rw_test() {
260 let target = CompactString::from(TARGET);
261
262 let named_tempfile = NamedTempFile::new().unwrap();
263 let path = named_tempfile.path();
264
265 let metadata_vec = [
266 CrateInfo {
267 name: "a".into(),
268 version_req: "*".into(),
269 current_version: Version::new(0, 1, 0),
270 source: CrateSource::cratesio_registry(),
271 target: target.clone(),
272 bins: vec!["1".into(), "2".into()],
273 },
274 CrateInfo {
275 name: "b".into(),
276 version_req: "0.1.0".into(),
277 current_version: Version::new(0, 1, 0),
278 source: CrateSource::cratesio_registry(),
279 target: target.clone(),
280 bins: vec!["1".into(), "2".into()],
281 },
282 CrateInfo {
283 name: "a".into(),
284 version_req: "*".into(),
285 current_version: Version::new(0, 2, 0),
286 source: CrateSource::cratesio_registry(),
287 target: target.clone(),
288 bins: vec!["1".into()],
289 },
290 ];
291
292 append_to_path(path, metadata_vec.clone()).unwrap();
293
294 let mut iter = metadata_vec.into_iter();
295 iter.next().unwrap();
296
297 let mut metadata_set: BTreeSet<_> = iter.collect();
298
299 let mut records = Records::load_from_path(path).unwrap();
300 assert_records_eq!(&records, &metadata_set);
301
302 assert!(records.remove("b"));
303 metadata_set.remove("b");
304 assert_eq!(records.len(), metadata_set.len());
305 records.overwrite().unwrap();
306
307 let records = Records::load_from_path(path).unwrap();
308 assert_records_eq!(&records, &metadata_set);
309 drop(records);
311
312 let new_metadata = CrateInfo {
313 name: "b".into(),
314 version_req: "0.1.0".into(),
315 current_version: Version::new(0, 1, 1),
316 source: CrateSource::cratesio_registry(),
317 target,
318 bins: vec!["1".into(), "2".into()],
319 };
320 append_to_path(path, [new_metadata.clone()]).unwrap();
321 metadata_set.insert(new_metadata);
322
323 let records = Records::load_from_path(path).unwrap();
324 assert_records_eq!(&records, &metadata_set);
325 }
326}