1use std::collections::{BTreeMap, HashMap};
5use std::io;
6use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
7
8use rig_core::conversation::{Change, ChangeRecord, Conversation, Lineage, MessageId};
9use serde_json::Value;
10use sha2::{Digest, Sha256};
11
12use crate::blobs::{PutBlob, put_blob, read_blob, store_change};
13use crate::fork::turn_cut;
14use crate::format::{Header, LogLines, Meta, SESSION, write_change, write_meta};
15use crate::session::{
16 Fold, Folded, fold_all, fold_lines, log_lines, read_header, read_logs, repairs, retired,
17 session_of,
18};
19use crate::store::now;
20use crate::{LogWriteError, Repair, SavedAgent, Session, SessionError, Stamp, Store};
21use rig_core::now_ms;
22
23#[derive(Default)]
25pub(crate) struct Log {
26 depth: usize,
28 next_seq: u64,
29 started: bool,
31 pending: Vec<u8>,
32 state: BTreeMap<String, Value>,
35 lineage: Lineage,
37 changes: u64,
39 len: u64,
42}
43
44#[derive(Default)]
46pub(crate) struct Book {
47 next_n: u64,
49 live: bool,
51 failure: Option<String>,
54 wrote: bool,
56 logs: HashMap<String, Log>,
57 torn: HashMap<String, u64>,
60}
61
62impl Book {
63 pub fn after(folded: &[Folded]) -> Self {
65 let parents: HashMap<&str, Option<&str>> = folded
66 .iter()
67 .map(|log| (log.name.as_str(), log.header.parent.as_deref()))
68 .collect();
69 let mut book = Self::default();
70 for log in folded {
71 let ancestors = std::iter::successors(log.header.parent.as_deref(), |parent| {
73 parents.get(parent).copied().flatten()
74 });
75 let known = ancestors.take_while(|parent| parents.contains_key(parent));
76 let kept = Log {
77 depth: known.take(folded.len()).count(),
78 next_seq: log.next_seq,
79 started: true,
80 state: log.state.clone(),
81 lineage: log.header.lineage,
82 changes: log.conversation.revision(),
83 len: log.len,
84 ..Log::default()
85 };
86 book.logs.insert(log.name.clone(), kept);
87 book.next_n = book.next_n.max(log.next_n);
88 }
89 book
90 }
91
92 pub fn set_aside(&mut self, torn: impl IntoIterator<Item = (String, u64)>) {
95 self.torn.extend(torn);
96 }
97
98 pub fn start(&mut self) {
99 self.live = true;
100 }
101
102 fn is_live(&self) -> bool {
103 self.live && self.failure.is_none()
104 }
105
106 pub fn opened(&mut self, name: &str, parent: Option<&str>) -> Option<&mut Log> {
109 if !self.is_live() {
110 return None;
111 }
112 if !self.logs.contains_key(name) {
113 let depth = |parent| self.logs.get(parent).map_or(0, |log: &Log| log.depth + 1);
114 let header = Header::new(name, parent);
115 let mut log = Log {
116 depth: parent.map_or(0, depth),
117 lineage: header.lineage,
118 ..Log::default()
119 };
120 let header = Meta::Header(header);
121 let stamp = Stamp {
122 n: self.next_n,
123 seq: 0,
124 t: now_ms(),
125 };
126 if let Err(failure) = write_meta(&mut log.pending, stamp, &header) {
127 self.failure = Some(failure.to_string());
128 return None;
129 }
130 log.next_seq += 1;
131 self.next_n += 1;
132 self.logs.insert(name.to_owned(), log);
133 }
134 self.logs.get_mut(name)
135 }
136
137 fn meta(&mut self, name: &str, meta: &Meta) -> bool {
139 if self.opened(name, None).is_none() {
140 return false;
141 }
142 let n = self.next_n;
143 let Some(log) = self.logs.get_mut(name) else {
144 return false;
145 };
146 let stamp = Stamp {
147 n,
148 seq: log.next_seq,
149 t: now_ms(),
150 };
151 match write_meta(&mut log.pending, stamp, meta) {
152 Ok(()) => {
153 log.next_seq += 1;
154 self.next_n += 1;
155 true
156 }
157 Err(failure) => {
158 self.failure = Some(failure.to_string());
159 false
160 }
161 }
162 }
163
164 pub fn save(
172 &mut self,
173 put: &mut PutBlob<'_>,
174 name: &str,
175 conversation: &mut Conversation,
176 ) -> Vec<ChangeRecord> {
177 let Some(log) = self.opened(name, None) else {
178 return stamp_all(conversation.take_unsaved());
179 };
180 let (lineage, held) = (log.lineage, log.changes);
181 let continues = conversation.continues(lineage, held);
182 let unsaved = conversation.take_unsaved();
183 let changes = if continues {
184 unsaved
185 } else {
186 vec![ChangeRecord {
187 change: Change::Reset {
188 state: Box::new(conversation.view()),
189 },
190 by: None,
191 at: None,
192 }]
193 };
194 if changes.is_empty() {
195 conversation.continue_log(lineage, held);
196 return changes;
197 }
198 let mut queued = Vec::with_capacity(changes.len());
199 for recorded in stamp_all(changes) {
200 if let Err(failure) = self.queue(put, name, &recorded) {
201 self.failure = Some(failure);
202 break;
203 }
204 queued.push(recorded);
205 }
206 let held = held.saturating_add(queued.len() as u64);
207 if let Some(log) = self.logs.get_mut(name) {
208 log.started = true;
209 log.changes = held;
210 }
211 conversation.continue_log(lineage, held);
212 queued
213 }
214
215 fn queue(
217 &mut self,
218 put: &mut PutBlob<'_>,
219 name: &str,
220 recorded: &ChangeRecord,
221 ) -> Result<(), String> {
222 let stored = store_change(&recorded.change, put)
223 .map_err(|failure| format!("storing an image failed: {failure}"))?;
224 let change = stored.as_ref().unwrap_or(&recorded.change);
225 let n = self.next_n;
226 let log = self.logs.get_mut(name).ok_or("no log")?;
227 let stamp = Stamp {
228 n,
229 seq: log.next_seq,
230 t: recorded.at.unwrap_or_default(),
231 };
232 write_change(&mut log.pending, stamp, recorded.by.as_ref(), change)
233 .map_err(|failure| failure.to_string())?;
234 log.next_seq += 1;
235 self.next_n += 1;
236 Ok(())
237 }
238
239 pub fn save_value(&mut self, name: &str, key: &str, value: Option<Value>, starts: bool) {
243 let value = value.filter(|value| !value.is_null());
244 let known = self.logs.get(name).and_then(|log| log.state.get(key));
245 if known == value.as_ref() {
246 return;
247 }
248 let meta = Meta::State {
249 key: key.to_owned(),
250 value: value.clone(),
251 };
252 if self.meta(name, &meta)
253 && let Some(log) = self.logs.get_mut(name)
254 {
255 log.started |= starts;
256 match value {
257 Some(value) => log.state.insert(key.to_owned(), value),
258 None => log.state.remove(key),
259 };
260 }
261 }
262
263 pub fn states(
266 &mut self,
267 name: &str,
268 known: Option<&BTreeMap<String, Value>>,
269 values: &BTreeMap<String, Value>,
270 ) {
271 let removed = known.into_iter().flat_map(BTreeMap::keys);
272 let removed: Vec<String> = removed
273 .filter(|key| !values.contains_key(*key))
274 .cloned()
275 .collect();
276 for key in removed {
277 self.save_value(name, &key, None, true);
278 }
279 for (key, value) in values {
280 self.save_value(name, key, Some(value.clone()), true);
281 }
282 }
283
284 pub fn fork(
290 &mut self,
291 store: &dyn Store,
292 (from, to): (&str, &str),
293 through: Option<MessageId>,
294 ) -> Result<Folded, SessionError> {
295 let refuse = |why: String| SessionError::from(io::Error::other(why));
296 if !self.is_live() {
297 return Err(refuse("the session is not logged".to_owned()));
298 }
299 if self.logs.contains_key(to) || log_lines(store, to)?.is_some() {
300 return Err(refuse(format!("there is a log `{to}` already")));
301 }
302 let lines = log_lines(store, from)?.unwrap_or_else(|| LogLines::parse(Vec::new()));
303 let mut bytes = Vec::new();
304 if let Some((_, start, first)) = lines.iter().next() {
305 let (stamp, _) = read_header(from, first)?;
306 let cut = turn_cut(from, &lines, through)?;
307 let stamp = Stamp {
308 t: now_ms(),
309 ..stamp
310 };
311 let header = Meta::Header(Header::new(to, None));
312 write_meta(&mut bytes, stamp, &header).map_err(io::Error::from)?;
313 let start = usize::try_from(start).unwrap_or(usize::MAX);
314 bytes.extend_from_slice(lines.prefix(cut).get(start..).unwrap_or_default());
315 } else if through.is_some() {
316 return Err(refuse(format!("the log `{from}` holds no message")));
317 }
318 if bytes.is_empty() {
319 let _ = self
320 .opened(to, None)
321 .ok_or_else(|| refuse("logging stopped".to_owned()))?;
322 let folded = Folded {
323 name: to.to_owned(),
324 ..Folded::default()
325 };
326 return Ok(folded);
327 }
328 let copy = LogLines::parse(bytes);
329 let read = |blob: &str| read_blob(store, blob);
330 let folded = fold_lines(to, ©, Fold::WHOLE, &read)?
331 .ok_or_else(|| refuse(format!("the copy of the log `{from}` is empty")))?;
332 refused(to, store.append_log(to, 0, copy.prefix(u64::MAX)))?;
333 let log = Log {
334 next_seq: folded.next_seq,
335 started: true,
336 state: folded.state.clone(),
337 lineage: folded.header.lineage,
338 changes: folded.conversation.revision(),
339 len: copy.len(),
340 ..Log::default()
341 };
342 self.logs.insert(to.to_owned(), log);
343 Ok(folded)
344 }
345
346 pub fn retire(&mut self, name: &str) {
347 self.meta(name, &Meta::Retired);
348 }
349
350 pub fn flush(&mut self, store: &dyn Store) -> Result<(), SessionError> {
356 now(self.flush_async(store))
357 }
358
359 pub async fn flush_async<S: crate::AsyncStore + ?Sized>(
361 &mut self,
362 store: &S,
363 ) -> Result<(), SessionError> {
364 if let Some(failure) = &self.failure {
365 return Err(io::Error::other(failure.clone()).into());
366 }
367 let any = self.logs.values().any(|log| log.started);
368 let mut due: Vec<(usize, String)> = self
369 .logs
370 .iter()
371 .filter(|(name, log)| log.started || any && name.as_str() == SESSION)
372 .filter(|(_, log)| !log.pending.is_empty())
373 .map(|(name, log)| (log.depth, name.clone()))
374 .collect();
375 due.sort_by(|a, b| b.cmp(a));
376 for (_, name) in due {
377 let Some(log) = self.logs.get_mut(&name) else {
378 continue;
379 };
380 let mut written = Ok(());
381 if let Some(cut) = self.torn.remove(&name) {
382 written = set_aside_tail_async(store, &name, log.len, cut)
383 .await
384 .map(drop);
385 }
386 if written.is_ok() {
387 let appended = store.append_log_async(&name, log.len, &log.pending).await;
388 written = refused(&name, appended);
389 }
390 if let Err(failure) = written {
391 let why = format!("writing the log {name} failed: {failure}");
392 self.failure = Some(why.clone());
393 return Err(match failure {
394 SessionError::Changed { .. } => failure,
395 _ => io::Error::other(why).into(),
396 });
397 }
398 log.len += log.pending.len() as u64;
399 log.pending.clear();
400 self.wrote = true;
401 }
402 Ok(())
403 }
404}
405
406#[derive(Clone, Default)]
411pub struct Writer(Arc<Mutex<Inner>>);
412
413#[derive(Default)]
414struct Inner {
415 store: Option<Arc<dyn Store>>,
416 book: Book,
417}
418
419impl Writer {
420 fn inner(&self) -> MutexGuard<'_, Inner> {
421 self.0.lock().unwrap_or_else(PoisonError::into_inner)
422 }
423
424 pub fn open(store: Arc<dyn Store>) -> Result<(Self, Session), SessionError> {
437 let mut folded = fold_all(&*store, &read_logs(&*store)?)?;
438 let mut repairs = repairs(&*store, &folded)?;
439 for repair in &mut repairs {
440 if let Repair::TornTail {
441 log,
442 kept,
443 cut,
444 set_aside,
445 } = repair
446 {
447 *set_aside = Some(set_aside_tail(&*store, log, *kept, *cut)?);
448 }
449 }
450 let book = Book::after(&folded);
451 let skipped = retired(&folded);
452 folded.retain(|log| !skipped.contains(&log.name));
453 let writer = Self(Arc::new(Mutex::new(Inner {
454 store: Some(store),
455 book,
456 })));
457 Ok((writer, session_of(folded, repairs)))
458 }
459
460 pub fn start(&self) {
463 let mut inner = self.inner();
464 if inner.store.is_some() {
465 inner.book.start();
466 }
467 }
468
469 pub fn is_live(&self) -> bool {
472 self.inner().book.is_live()
473 }
474
475 pub fn failure(&self) -> Option<String> {
477 self.inner().book.failure.clone()
478 }
479
480 pub fn has_written(&self) -> bool {
483 self.inner().book.wrote
484 }
485
486 pub fn open_log(&self, log: &str, parent: Option<&str>) {
489 let _ = self.inner().book.opened(log, parent);
490 }
491
492 pub fn save(&self, log: &str, conversation: &mut Conversation) -> Vec<ChangeRecord> {
499 let mut inner = self.inner();
500 let Inner { store, book } = &mut *inner;
501 match store {
502 Some(store) => {
503 let put = &mut |name: String, bytes: Vec<u8>| put_blob(&**store, &name, &bytes);
504 book.save(put, log, conversation)
505 }
506 None => stamp_all(conversation.take_unsaved()),
507 }
508 }
509
510 pub fn save_value(&self, log: &str, key: &str, value: Option<Value>, starts: bool) {
514 self.inner().book.save_value(log, key, value, starts);
515 }
516
517 pub fn fork_log(
533 &self,
534 from: &str,
535 to: &str,
536 through: Option<MessageId>,
537 ) -> Result<SavedAgent, SessionError> {
538 let mut inner = self.inner();
539 let Inner { store, book } = &mut *inner;
540 let Some(store) = store else {
541 return Err(io::Error::other("the session is not stored").into());
542 };
543 book.flush(&**store)?;
544 let folded = book.fork(&**store, (from, to), through)?;
545 Ok(SavedAgent {
546 id: folded.name,
547 started_ms: folded.started_ms,
548 conversation: folded.conversation,
549 saved: folded.state,
550 ..SavedAgent::default()
551 })
552 }
553
554 pub fn retire(&self, log: &str) {
556 self.inner().book.retire(log);
557 }
558
559 pub fn flush(&self) -> Result<(), SessionError> {
571 let mut inner = self.inner();
572 let Inner { store, book } = &mut *inner;
573 match store {
574 Some(store) => book.flush(&**store),
575 None => Ok(()),
576 }
577 }
578}
579
580pub(crate) fn set_aside_tail(
591 store: &dyn Store,
592 log: &str,
593 kept: u64,
594 cut: u64,
595) -> Result<String, SessionError> {
596 now(set_aside_tail_async(store, log, kept, cut))
597}
598
599async fn set_aside_tail_async<S: crate::AsyncStore + ?Sized>(
601 store: &S,
602 log: &str,
603 kept: u64,
604 cut: u64,
605) -> Result<String, SessionError> {
606 let bytes = store.read_log_async(log).await?;
607 let split = usize::try_from(kept)
608 .ok()
609 .and_then(|kept| bytes.split_at_checked(kept));
610 let Some((prefix, tail)) = split.filter(|(_, tail)| tail.len() as u64 == cut) else {
611 let log = log.to_owned();
612 return Err(SessionError::Changed { log });
613 };
614 let name = format!("recovered/{log}-{:x}.part", Sha256::digest(tail));
615 store.write_file_async(&name, tail).await?;
616 let replaced = store.replace_log_async(log, kept + cut, prefix).await;
617 refused(log, replaced)?;
618 Ok(name)
619}
620
621pub(crate) fn refused(log: &str, written: Result<(), LogWriteError>) -> Result<(), SessionError> {
624 written.map_err(|failure| match failure {
625 LogWriteError::Changed => SessionError::Changed {
626 log: log.to_owned(),
627 },
628 LogWriteError::Io(failure) => failure.into(),
629 })
630}
631
632fn stamp_all(changes: Vec<ChangeRecord>) -> Vec<ChangeRecord> {
634 let now = now_ms();
635 let stamp = |recorded: ChangeRecord| ChangeRecord {
636 at: Some(recorded.at.unwrap_or(now)),
637 ..recorded
638 };
639 changes.into_iter().map(stamp).collect()
640}