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 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 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}