1use std::collections::BTreeMap;
4
5use chrono::Local;
6use rmux_proto::RmuxError;
7
8use crate::vis::encode_buffer_sample;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12struct BufferEntry {
13 content: Vec<u8>,
15 order: u64,
17 created: i64,
19 unnamed: bool,
21}
22
23#[derive(Debug, Clone, Default)]
31pub struct BufferStore {
32 buffers: BTreeMap<String, BufferEntry>,
34 next_unnamed_id: u32,
36 next_order: u64,
38}
39
40impl BufferStore {
41 #[must_use]
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 #[must_use]
49 pub fn len(&self) -> usize {
50 self.buffers.len()
51 }
52
53 #[must_use]
55 pub fn is_empty(&self) -> bool {
56 self.buffers.is_empty()
57 }
58
59 #[must_use]
61 pub fn stack_head(&self) -> Option<&str> {
62 self.buffers
63 .iter()
64 .max_by_key(|(_, entry)| entry.order)
65 .map(|(name, _)| name.as_str())
66 }
67
68 #[must_use]
70 pub fn top_unnamed(&self) -> Option<&str> {
71 self.buffers
72 .iter()
73 .filter(|(_, entry)| entry.unnamed)
74 .max_by_key(|(_, entry)| entry.order)
75 .map(|(name, _)| name.as_str())
76 }
77
78 #[must_use]
80 pub fn get(&self, name: &str) -> Option<&[u8]> {
81 self.buffers.get(name).map(|entry| entry.content.as_slice())
82 }
83
84 pub fn set(
95 &mut self,
96 name: Option<&str>,
97 content: Vec<u8>,
98 buffer_limit: usize,
99 ) -> Result<SetBufferOutcome, RmuxError> {
100 if let Some(name) = name {
101 validate_buffer_name(name)?;
102 }
103 if content.is_empty() {
104 return Ok(SetBufferOutcome {
105 buffer_name: None,
106 evicted: Vec::new(),
107 });
108 }
109
110 let order = self.next_order;
111 self.next_order += 1;
112 let created = Local::now().timestamp();
113
114 let (buffer_name, is_new_unnamed) = match name {
115 Some(name) => {
116 self.buffers.insert(
117 name.to_owned(),
118 BufferEntry {
119 content,
120 order,
121 created,
122 unnamed: false,
123 },
124 );
125 (name.to_owned(), false)
126 }
127 None => {
128 let buffer_name = self.allocate_unnamed_name()?;
129 self.buffers.insert(
130 buffer_name.clone(),
131 BufferEntry {
132 content,
133 order,
134 created,
135 unnamed: true,
136 },
137 );
138 (buffer_name, true)
139 }
140 };
141
142 let mut evicted = Vec::new();
143 if is_new_unnamed && buffer_limit > 0 {
144 let unnamed_count = self.buffers.values().filter(|entry| entry.unnamed).count();
145 if unnamed_count > buffer_limit {
146 let to_evict = unnamed_count - buffer_limit;
147 let mut unnamed_by_order: Vec<(String, u64)> = self
148 .buffers
149 .iter()
150 .filter(|(_, entry)| entry.unnamed)
151 .map(|(name, entry)| (name.clone(), entry.order))
152 .collect();
153 unnamed_by_order.sort_by_key(|(_, order)| *order);
154
155 for (name, _) in unnamed_by_order.into_iter().take(to_evict) {
156 self.buffers.remove(&name);
157 evicted.push(name);
158 }
159 }
160 }
161
162 Ok(SetBufferOutcome {
163 buffer_name: Some(buffer_name),
164 evicted,
165 })
166 }
167
168 pub fn rename(
170 &mut self,
171 old_name: Option<&str>,
172 new_name: &str,
173 ) -> Result<RenameBufferOutcome, RmuxError> {
174 validate_buffer_name(new_name)?;
175
176 let old_name = match old_name {
177 Some(name) => {
178 if !self.buffers.contains_key(name) {
179 return Err(RmuxError::Server(format!("no buffer {name}")));
180 }
181 name.to_owned()
182 }
183 None => self
184 .top_unnamed()
185 .map(str::to_owned)
186 .ok_or_else(|| RmuxError::Server("no buffer".to_owned()))?,
187 };
188
189 if old_name == new_name {
190 return Ok(RenameBufferOutcome {
191 old_name,
192 new_name: new_name.to_owned(),
193 replaced: false,
194 changed: false,
195 });
196 }
197
198 let replaced = self.buffers.remove(new_name).is_some();
199 let mut entry = self
200 .buffers
201 .remove(&old_name)
202 .expect("rename source existence was prevalidated");
203 entry.unnamed = false;
204 self.buffers.insert(new_name.to_owned(), entry);
205
206 Ok(RenameBufferOutcome {
207 old_name,
208 new_name: new_name.to_owned(),
209 replaced,
210 changed: true,
211 })
212 }
213
214 pub fn delete(&mut self, name: Option<&str>) -> Result<String, RmuxError> {
219 let target = match name {
220 Some(name) => name.to_owned(),
221 None => self
222 .stack_head()
223 .map(str::to_owned)
224 .ok_or_else(|| RmuxError::Server("no buffers".to_owned()))?,
225 };
226
227 if self.buffers.remove(&target).is_none() {
228 return Err(RmuxError::Server(format!("no buffer {target}")));
229 }
230
231 Ok(target)
232 }
233
234 pub fn delete_if_order_matches(&mut self, name: &str, order: u64) -> bool {
239 if self
240 .buffers
241 .get(name)
242 .is_some_and(|entry| entry.order == order)
243 {
244 self.buffers.remove(name);
245 true
246 } else {
247 false
248 }
249 }
250
251 pub fn show(&self, name: Option<&str>) -> Result<(&str, &[u8]), RmuxError> {
254 let (name, content, _) = self.show_with_order(name)?;
255 Ok((name, content))
256 }
257
258 pub fn show_with_order(&self, name: Option<&str>) -> Result<(&str, &[u8], u64), RmuxError> {
260 let resolved = match name {
261 Some(name) => {
262 if !self.buffers.contains_key(name) {
263 return Err(RmuxError::Server(format!("no buffer {name}")));
264 }
265 name.to_owned()
266 }
267 None => self
268 .stack_head()
269 .ok_or_else(|| RmuxError::Server("no buffers".to_owned()))?
270 .to_owned(),
271 };
272
273 let (key, entry) = self
274 .buffers
275 .get_key_value(&resolved)
276 .expect("buffer existence was verified above");
277 Ok((key.as_str(), &entry.content, entry.order))
278 }
279
280 #[must_use]
282 pub fn entries(&self) -> Vec<BufferView<'_>> {
283 self.buffers
284 .iter()
285 .map(|(name, entry)| BufferView {
286 name,
287 content: &entry.content,
288 order: entry.order,
289 created: entry.created,
290 })
291 .collect()
292 }
293
294 #[must_use]
296 pub fn list(&self) -> Vec<String> {
297 let mut entries = self.entries();
298 entries.sort_by_key(|entry| std::cmp::Reverse(entry.order));
299 entries
300 .into_iter()
301 .map(|entry| entry.default_line())
302 .collect()
303 }
304
305 fn allocate_unnamed_name(&mut self) -> Result<String, RmuxError> {
306 loop {
307 let id = self.next_unnamed_id;
308 self.next_unnamed_id = self
309 .next_unnamed_id
310 .checked_add(1)
311 .ok_or_else(|| RmuxError::Server("unnamed buffer sequence exhausted".to_owned()))?;
312 let name = format!("buffer{id}");
313 if !self.buffers.contains_key(&name) {
314 return Ok(name);
315 }
316 }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct SetBufferOutcome {
323 buffer_name: Option<String>,
325 evicted: Vec<String>,
327}
328
329impl SetBufferOutcome {
330 #[must_use]
332 pub fn buffer_name(&self) -> Option<&str> {
333 self.buffer_name.as_deref()
334 }
335
336 #[must_use]
338 pub fn evicted(&self) -> &[String] {
339 &self.evicted
340 }
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct RenameBufferOutcome {
346 old_name: String,
347 new_name: String,
348 replaced: bool,
349 changed: bool,
350}
351
352impl RenameBufferOutcome {
353 #[must_use]
355 pub fn old_name(&self) -> &str {
356 &self.old_name
357 }
358
359 #[must_use]
361 pub fn new_name(&self) -> &str {
362 &self.new_name
363 }
364
365 #[must_use]
367 pub const fn replaced(&self) -> bool {
368 self.replaced
369 }
370
371 #[must_use]
373 pub const fn changed(&self) -> bool {
374 self.changed
375 }
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub struct BufferView<'a> {
381 name: &'a str,
382 content: &'a [u8],
383 order: u64,
384 created: i64,
385}
386
387impl<'a> BufferView<'a> {
388 #[must_use]
390 pub fn name(&self) -> &'a str {
391 self.name
392 }
393
394 #[must_use]
396 pub fn content(&self) -> &'a [u8] {
397 self.content
398 }
399
400 #[must_use]
402 pub const fn order(&self) -> u64 {
403 self.order
404 }
405
406 #[must_use]
408 pub const fn created(&self) -> i64 {
409 self.created
410 }
411
412 #[must_use]
414 pub fn size(&self) -> usize {
415 self.content.len()
416 }
417
418 #[must_use]
420 pub fn sample(&self) -> String {
421 buffer_preview(self.content)
422 }
423
424 #[must_use]
426 pub fn default_line(&self) -> String {
427 format!(
428 "{}: {} bytes: \"{}\"",
429 self.name(),
430 self.size(),
431 self.sample()
432 )
433 }
434}
435
436fn validate_buffer_name(name: &str) -> Result<(), RmuxError> {
438 if name.is_empty() {
439 return Err(RmuxError::Server(
440 "buffer name must not be empty".to_owned(),
441 ));
442 }
443 Ok(())
444}
445
446fn buffer_preview(content: &[u8]) -> String {
448 encode_buffer_sample(content)
449}
450
451#[cfg(test)]
452#[path = "buffers/tests.rs"]
453mod tests;