free_launch/
invocation.rs1use chrono::Utc;
2use color_eyre::Result;
3use csv::WriterBuilder;
4use serde::{Deserialize, Serialize};
5use std::{
6 fs::{File, OpenOptions},
7 path::{Path, PathBuf},
8 time::SystemTime,
9};
10
11use crate::{free_launch::PROJECT_DIRS, launch_entry::LaunchEntry};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Invocation {
15 pub timestamp: u64,
16 pub search_query: String,
17 pub action: String,
18 pub item_name: String,
19 pub item_id: String,
20 pub item_path: String,
21}
22
23impl Invocation {
24 pub fn new(search_query: &str, action: String, launch_entry: &LaunchEntry) -> Self {
25 let now: SystemTime = SystemTime::now();
28 let timestamp = now
29 .duration_since(SystemTime::UNIX_EPOCH)
30 .unwrap_or_default()
31 .as_secs();
32
33 Self {
34 timestamp,
35 search_query: search_query.to_owned(),
36 action,
37 item_name: launch_entry.name().to_owned(),
38 item_id: launch_entry.id().map(|i| i.to_owned()).unwrap_or_default(),
39 item_path: launch_entry
40 .file_path()
41 .map(|i| i.to_string_lossy().to_string())
42 .unwrap_or_default(),
43 }
44 }
45
46 pub(crate) fn id(&self) -> &str {
47 &self.item_id
48 }
49
50 pub(crate) fn name(&self) -> &str {
51 &self.item_name
52 }
53
54 fn cache_file() -> PathBuf {
55 let date_str = Utc::now().format("%Y-%m-%d").to_string();
57 let log_filename = format!("free-launch-invocations-{}.tsv", date_str);
58 PROJECT_DIRS.cache_dir().join(log_filename)
59 }
60
61 pub(crate) fn log_entry(
62 search_query: &str,
63 action: String,
64 launch_entry: &LaunchEntry,
65 ) -> Result<()> {
66 Invocation::new(search_query, action, launch_entry).append_to_csv(&Self::cache_file())
67 }
68
69 fn append_to_csv(&self, file_path: &Path) -> Result<()> {
70 let write_headers = !file_path.exists();
72
73 let file = OpenOptions::new()
76 .append(true)
77 .create(true)
78 .open(file_path)?;
79
80 let mut wtr = WriterBuilder::new()
83 .has_headers(write_headers)
84 .from_writer(file);
85
86 wtr.serialize(self)?;
88
89 wtr.flush()?;
91 Ok(())
92 }
93
94 pub(crate) fn load_from_cache() -> Result<Vec<Invocation>> {
95 let mut rdr = csv::Reader::from_reader(File::open(Self::cache_file())?);
97 let mut records = Vec::new();
98
99 for result in rdr.deserialize() {
101 let record: Invocation = result?;
102 records.push(record);
103 }
104
105 Ok(records)
106 }
107}