Skip to main content

wapm_cli/data/
wax_index.rs

1//! The Wax index is where temporary commands are tracked for use with the `wax`
2//! command.
3
4use crate::config;
5use crate::constants::RFC3339_FORMAT_STRING;
6use semver::Version;
7use std::convert::From;
8use std::env;
9use std::fs;
10use std::io::{self, Read, Write};
11use std::path::{Path, PathBuf};
12use thiserror::Error;
13
14use std::collections::HashMap;
15
16#[derive(Debug, Deserialize, Serialize)]
17pub struct WaxIndex {
18    base_dir: PathBuf,
19    index: HashMap<String, WaxEntry>,
20}
21
22#[derive(Debug, Deserialize, Serialize)]
23struct WaxEntry {
24    /// Fully qualified package name `namespace/name@version`
25    package_name: String,
26    /// Timestamp when wax was last updated
27    last_updated: String,
28}
29
30impl WaxEntry {
31    fn new(name: String, version: Version, time: String) -> Self {
32        WaxEntry {
33            package_name: format!("{}@{}", name, version),
34            last_updated: time,
35        }
36    }
37}
38
39impl WaxIndex {
40    /// Read the `WaxIndex` from disk
41    pub fn open() -> Result<Self, WaxIndexError> {
42        trace!("Loading WaxIndex!");
43        let wax_path = config::Config::get_wax_file_path()?;
44        let wax_index = if wax_path.exists() {
45            let mut f = fs::OpenOptions::new().read(true).open(wax_path)?;
46
47            let index_str = {
48                let mut s = String::new();
49                f.read_to_string(&mut s)?;
50                s
51            };
52
53            if index_str.is_empty() {
54                WaxIndex {
55                    index: Default::default(),
56                    base_dir: env::temp_dir().join("wax"),
57                }
58            } else {
59                serde_json::from_str(&index_str)?
60            }
61        } else {
62            WaxIndex {
63                index: Default::default(),
64                base_dir: env::temp_dir().join("wax"),
65            }
66        };
67
68        // ensure the directory exists
69        fs::create_dir_all(&wax_index.base_dir)?;
70        trace!("WaxIndex created!");
71
72        Ok(wax_index)
73    }
74
75    /// Save the `WaxIndex` to disk
76    pub fn save(&self) -> Result<(), WaxIndexError> {
77        trace!("Saving WaxIndex!");
78        let wax_path = config::Config::get_wax_file_path()?;
79        let mut f = fs::OpenOptions::new()
80            .write(true)
81            .create(true)
82            .truncate(true)
83            .open(wax_path)?;
84
85        let json_str = serde_json::to_string(self)?;
86
87        f.write_all(json_str.as_bytes())?;
88        trace!("WaxIndex saved!");
89        Ok(())
90    }
91
92    /// This function takes a `&mut` because it will update itself with the
93    /// information that it finds.
94    pub fn search_for_entry(
95        &mut self,
96        entry: String,
97    ) -> Result<(String, Version, time::Timespec), WaxIndexError> {
98        if let Some(WaxEntry {
99            package_name,
100            last_updated,
101        }) = self.index.get(&entry)
102        {
103            let location = self.base_path().join(package_name);
104            // check if entry still exists and if not remove it
105            if location.exists() {
106                trace!("Wax entry found and it still exists!");
107                let mut splitter = package_name.split('@');
108                let package_name = splitter
109                    .next()
110                    .ok_or_else(|| WaxIndexError::EntryCorrupt {
111                        entry: entry.clone(),
112                    })?
113                    .to_string();
114                let version = splitter
115                    .next()
116                    .and_then(|v| Version::parse(v).ok())
117                    .ok_or_else(|| WaxIndexError::EntryCorrupt {
118                        entry: entry.clone(),
119                    })?;
120                let last_seen =
121                    time::strptime(last_updated, RFC3339_FORMAT_STRING).map_err(|e| {
122                        WaxIndexError::EntryCorrupt {
123                            entry: e.to_string(),
124                        }
125                    })?;
126                return Ok((package_name, version, last_seen.to_timespec()));
127            }
128            trace!("Wax entry found but it no longer exists, removing from registry!");
129            self.index.remove(&entry);
130        }
131        Err(WaxIndexError::EntryNotFound {
132            entry: entry.clone(),
133        })
134    }
135
136    /// Package installed, add it to the index.
137    ///
138    /// Returns true if an existing entry was updated.
139    pub fn insert_entry(&mut self, entry: String, version: Version, package_name: String) -> bool {
140        let now = time::now_utc();
141        let now_str = time::strftime(RFC3339_FORMAT_STRING, &now).expect("Format current time!");
142        self.index
143            .insert(entry, WaxEntry::new(package_name, version, now_str))
144            .is_some()
145    }
146
147    /// Get path at which packages should be installed.
148    pub fn base_path(&self) -> &Path {
149        &self.base_dir
150    }
151}
152
153#[derive(Debug, Error)]
154pub enum WaxIndexError {
155    #[error("Error finding Wax Index: {0}")]
156    ConfigError(config::GlobalConfigError),
157    #[error("Failed to operate on Wax index file: `{0}`")]
158    IoError(io::Error),
159    #[error("Failed to parse WaxIndex from JSON or convert WaxIndex to JSON: `{0}`")]
160    SerdeError(serde_json::error::Error),
161    #[error("Entry `{entry}` not found")]
162    EntryNotFound { entry: String },
163    #[error("Entry `{entry}` found but was corrupt")]
164    EntryCorrupt { entry: String },
165}
166
167impl From<config::GlobalConfigError> for WaxIndexError {
168    fn from(other: config::GlobalConfigError) -> Self {
169        WaxIndexError::ConfigError(other)
170    }
171}
172
173impl From<io::Error> for WaxIndexError {
174    fn from(other: io::Error) -> Self {
175        WaxIndexError::IoError(other)
176    }
177}
178
179impl From<serde_json::error::Error> for WaxIndexError {
180    fn from(other: serde_json::error::Error) -> Self {
181        WaxIndexError::SerdeError(other)
182    }
183}