usage_tracker/
lib.rs

1//! `usage_tracker` is a library that allows you to easily keep track of usages of something.
2//!
3//! In addition to the library, there also is a CLI.
4//!
5//! # Design
6//! The library mainly consists of the `UsageInformation` struct. `UsageInformation` internally uses
7//! `Usages` to keep track of individual objects. Both provide methods to interact with the stored
8//! data.
9//!
10//! You can use serde to serialize and deserialize `UsageInformation` and `Usages` instances.
11//!
12//! All methods of `UsageInformation`, that can fail, (`Usages` has no such methods) use
13//! `UsageTrackerError` as error type. The documentation of those methods lists all possible errors
14//! that can occur within that method.
15//!
16//! As far as I can tell, the library should not panic no matter what input you provide.
17
18mod usages;
19
20use chrono::{Duration, Utc};
21use serde::{Deserialize, Serialize};
22use std::collections::{btree_map::Entry::Occupied, BTreeMap};
23use thiserror::Error;
24pub use usages::Usages;
25
26/// All errors the library's public interface can return.
27#[derive(Error, Debug)]
28pub enum UsageTrackerError {
29    /// The loading (most likely parsing) of a RON file failed. Contains the root cause.
30    #[error("RON file could not be loaded")]
31    FileLoadErrorRon(#[source] ron::Error),
32
33    /// Tried to add a new object to keep track of, but object with same name is already tracked.
34    #[error("object \"{name}\" is already tracked")]
35    ObjectAlreadyTracked { name: String },
36
37    /// Tried to predict the need of a never used object.
38    #[error("object \"{name}\" has never been used")]
39    ObjectNeverUsed { name: String },
40
41    /// Tried to access an object that is not kept track of.
42    #[error("object \"{name}\" doesn't exist")]
43    ObjectNotTracked { name: String },
44}
45
46/// A struct that keeps the records for all tracked objects.
47#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
48pub struct UsageInformation {
49    usage_information: BTreeMap<String, Usages>,
50}
51
52impl UsageInformation {
53    /// Adds a new object to keep track of.
54    ///
55    /// # Possible errors
56    /// - `UsageTrackerError::ObjectAlreadyTracked`
57    pub fn add(&mut self, name: &String) -> Result<(), UsageTrackerError> {
58        if self.usage_information.contains_key(name) {
59            return Err(UsageTrackerError::ObjectAlreadyTracked {
60                name: name.to_owned(),
61            });
62        }
63
64        self.usage_information
65            .insert(name.to_owned(), Usages::new());
66
67        Ok(())
68    }
69
70    /// Removes **all** objects permanently.
71    pub fn clear(&mut self) {
72        self.usage_information.clear();
73    }
74
75    /// Provides a vector with all existing keys.
76    pub fn list(&self) -> Vec<&String> {
77        self.usage_information.keys().collect()
78    }
79
80    /// Provides read access to all stored data.
81    pub fn list_verbose(&self) -> &BTreeMap<String, Usages> {
82        &self.usage_information
83    }
84
85    /// Loads a UsageInformation object from a RON file.
86    ///
87    /// # Explanation
88    /// With v0.2, the data layout was changed. To make the transition from v0.1 easier for users,
89    /// this function was created. It is able to read the RON files produced by v0.1 and convert
90    /// them into the data structure of v0.2.
91    ///
92    /// # Deprecation
93    /// If it still exists by then, v1.0 will see this function removed.
94    ///
95    /// # Possible errors
96    /// - `UsageTrackerError::FileLoadErrorRon`
97    #[deprecated(
98        since = "0.2",
99        note = "please only use this function if you have to load files from v0.1"
100    )]
101    pub fn load_usage_information_from_ron_file<R>(rdr: R) -> Result<Self, UsageTrackerError>
102    where
103        R: std::io::Read,
104    {
105        Ok(Self {
106            usage_information: ron::de::from_reader(rdr)
107                .or_else(|e| return Err(UsageTrackerError::FileLoadErrorRon(e)))?,
108        })
109    }
110
111    /// Creates a new, empty UsageInformation object.
112    pub fn new() -> Self {
113        Self {
114            usage_information: BTreeMap::new(),
115        }
116    }
117
118    /// Removes usages from an object.
119    ///
120    /// If `before` is `None`, all usages are removed. Otherwise, only usages before `before` are
121    /// removed.
122    ///
123    /// # Possible errors:
124    /// - `UsageTrackerError::ObjectNotTracked`
125    pub fn prune(
126        &mut self,
127        name: &String,
128        before: &Option<chrono::DateTime<chrono::Utc>>,
129    ) -> Result<(), UsageTrackerError> {
130        if let Occupied(mut e) = self.usage_information.entry(name.to_owned()) {
131            let usages = e.get_mut();
132
133            if before.is_some() {
134                usages.prune(before.unwrap());
135            } else {
136                usages.clear();
137            }
138
139            return Ok(());
140        } else {
141            return Err(UsageTrackerError::ObjectNotTracked {
142                name: name.to_owned(),
143            });
144        }
145    }
146
147    /// Records a new usage of an object.
148    ///
149    /// # Possible errors
150    /// - `UsageTrackerError::ObjectNotTracked`
151    pub fn record_use(&mut self, name: &String, add_if_new: bool) -> Result<(), UsageTrackerError> {
152        if !add_if_new && !self.usage_information.contains_key(name) {
153            return Err(UsageTrackerError::ObjectNotTracked {
154                name: name.to_owned(),
155            });
156        }
157
158        self.usage_information
159            .entry(name.to_owned())
160            .or_insert(Usages::new())
161            .record_usage();
162        Ok(())
163    }
164
165    /// Removes a currently tracked object permanently.
166    pub fn remove(&mut self, name: &String) {
167        if self.usage_information.contains_key(name) {
168            self.usage_information.remove(name);
169        }
170    }
171
172    /// Calculates the number of usages of the specified object within the specified amount of time.
173    ///
174    /// This works by calculating how much the specified time frame is in comparison to the time
175    /// since the oldest recorded usage. This relationship is the multiplied by the number of total
176    /// uses, to calculate a specific number.
177    ///
178    /// # Possible errors
179    /// - `UsageTrackerError::ObjectNeverUsed`
180    /// - `UsageTrackerError::ObjectNotTracked`
181    pub fn usage(&self, name: &String, time_frame: &Duration) -> Result<f64, UsageTrackerError> {
182        if !self.usage_information.contains_key(name) {
183            return Err(UsageTrackerError::ObjectNotTracked {
184                name: name.to_owned(),
185            });
186        }
187
188        let ui = &self.usage_information[name].list();
189        if ui.is_empty() {
190            return Err(UsageTrackerError::ObjectNeverUsed {
191                name: name.to_owned(),
192            });
193        }
194
195        let time_since_first_use = Utc::now() - ui[0];
196        let percentage_of_time_since_first_use =
197            time_frame.num_seconds() as f64 / time_since_first_use.num_seconds() as f64;
198
199        Ok(percentage_of_time_since_first_use * ui.len() as f64)
200    }
201
202    /// Provides the usages for a specific object.
203    ///
204    /// # Possible errors
205    /// - `UsageTrackerError::ObjectNotTracked`
206    pub fn usages(&self, name: &String) -> Result<&Usages, UsageTrackerError> {
207        if !self.usage_information.contains_key(name) {
208            return Err(UsageTrackerError::ObjectNotTracked {
209                name: name.to_owned(),
210            });
211        }
212
213        Ok(&self.usage_information[name])
214    }
215}