quartz_cli/
history.rs

1use crate::{snippet, Ctx, QuartzError, QuartzResult};
2use std::fmt::Display;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Default)]
9pub struct Entry {
10    timestemp: i64,
11    handle: String,
12
13    /// List of exchanged HTTP messages
14    messages: Vec<String>,
15}
16
17#[derive(Default)]
18pub struct EntryBuilder {
19    timestemp: i64,
20    handle: Option<String>,
21    messages: Vec<String>,
22}
23
24pub struct History {
25    /// Entry timestemp identification
26    entries: Vec<i64>,
27}
28
29impl History {
30    pub fn new(ctx: &Ctx) -> QuartzResult<Self> {
31        let paths = std::fs::read_dir(Self::dir(ctx))?;
32        let mut timestemps: Vec<i64> = Vec::new();
33
34        for path in paths {
35            let timestemp = path?
36                .file_name()
37                .to_str()
38                .ok_or(QuartzError::Internal)?
39                .parse::<i64>()?;
40
41            timestemps.push(timestemp);
42        }
43
44        timestemps.sort();
45        timestemps.reverse();
46
47        Ok(Self {
48            entries: timestemps.clone(),
49        })
50    }
51
52    pub fn entries(&self, ctx: &Ctx) -> Vec<Entry> {
53        self.entries
54            .iter()
55            .filter_map(|timestemp| {
56                Entry::read(&History::dir(ctx).join(timestemp.to_string())).ok()
57            })
58            .collect()
59    }
60
61    pub fn dir(ctx: &Ctx) -> PathBuf {
62        ctx.path().join("user").join("history")
63    }
64
65    pub fn last(ctx: &Ctx) -> Option<Entry> {
66        let history = History::new(ctx).ok()?;
67
68        let entry = Entry::read(&History::dir(ctx).join(history.entries.first()?.to_string()));
69
70        entry.ok()
71    }
72
73    pub fn write(ctx: &Ctx, entry: Entry) -> QuartzResult {
74        let content = toml::to_string(&entry)?;
75
76        std::fs::OpenOptions::new()
77            .create(true)
78            .write(true)
79            .open(History::dir(ctx).join(entry.timestemp.to_string()))?
80            .write_all(content.as_bytes())?;
81
82        Ok(())
83    }
84}
85
86impl EntryBuilder {
87    pub fn handle<T>(&mut self, value: T) -> &mut Self
88    where
89        T: Into<String>,
90    {
91        self.handle = Some(value.into());
92        self
93    }
94
95    pub fn message<T>(&mut self, value: T) -> &mut Self
96    where
97        T: Into<snippet::Http>,
98    {
99        let m: snippet::Http = value.into();
100        self.messages.push(m.to_string());
101        self
102    }
103
104    pub fn message_raw(&mut self, value: String) -> &mut Self {
105        self.messages.push(value);
106        self
107    }
108
109    pub fn timestemp(&mut self, value: i64) -> &mut Self {
110        self.timestemp = value;
111        self
112    }
113
114    pub fn build(self) -> QuartzResult<Entry, QuartzError> {
115        let handle = self.handle.ok_or(QuartzError::Internal)?;
116
117        if self.timestemp == 0 || self.messages.is_empty() {
118            return Err(QuartzError::Internal);
119        }
120
121        Ok(Entry {
122            handle,
123            timestemp: self.timestemp,
124            messages: self.messages,
125        })
126    }
127}
128
129impl Entry {
130    pub fn builder() -> EntryBuilder {
131        EntryBuilder::default()
132    }
133
134    pub fn handle(&self) -> &str {
135        &self.handle
136    }
137
138    pub fn messages(&self) -> &Vec<String> {
139        &self.messages
140    }
141
142    pub fn read(path: &Path) -> QuartzResult<Self> {
143        let content = std::fs::read_to_string(path)?;
144
145        Ok(toml::from_str(&content)?)
146    }
147}
148
149impl Display for Entry {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        writeln!(f, "{}", self.handle)?;
152        write!(f, "{}", self.messages.join("\n"))?;
153
154        Ok(())
155    }
156}