1 // Copyright (C) 2025 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use std::{
16 collections::BTreeMap,
17 fs::{read_to_string, write},
18 path::Path,
19 };
20
21 use anyhow::Result;
22 use chrono::{DateTime, Days, Local};
23 use serde::{Deserialize, Serialize};
24
25 #[derive(Serialize, Deserialize, Default)]
26 pub struct UpdatesTried {
27 attempts: BTreeMap<String, CrateUpdate>,
28 }
29
30 #[derive(Serialize, Deserialize)]
31 struct CrateUpdate {
32 name: String,
33 version: String,
34 time: DateTime<Local>,
35 success: bool,
36 }
37
38 impl UpdatesTried {
39 /// Read updates-tried.json and prune old entries.
40 // TODO: Put this somewhere better than $CWD.
read() -> Result<Self>41 pub fn read() -> Result<Self> {
42 let mut parsed: UpdatesTried = if Path::new("updates-tried.json").exists() {
43 serde_json::from_str(read_to_string("updates-tried.json")?.as_str())?
44 } else {
45 UpdatesTried::default()
46 };
47 let now = chrono::Local::now();
48 parsed.attempts.retain(|_, u| u.time.checked_add_days(Days::new(14)).unwrap_or(now) > now);
49 Ok(parsed)
50 }
write(&self) -> Result<()>51 pub fn write(&self) -> Result<()> {
52 let mut contents = serde_json::to_string_pretty(self)?;
53 contents.push('\n');
54 write("updates-tried.json", contents)?;
55 Ok(())
56 }
contains(&self, name: &str, version: &str) -> bool57 pub fn contains(&self, name: &str, version: &str) -> bool {
58 self.attempts.contains_key(&key(name, version))
59 }
record(&mut self, name: String, version: String, success: bool) -> Result<()>60 pub fn record(&mut self, name: String, version: String, success: bool) -> Result<()> {
61 self.attempts.insert(
62 key(&name, &version),
63 CrateUpdate { name, version, time: chrono::Local::now(), success },
64 );
65 self.write()
66 }
67 }
68
key(name: &str, version: &str) -> String69 fn key(name: &str, version: &str) -> String {
70 format!("{name}-v{version}")
71 }
72