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}