// SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2026 Andy Dixon //! Session recording in asciicast v2 (https://docs.asciinema.org/manual/asciicast/v2/): a JSON //! header line, then one `[elapsed_secs, type, data]` array per line. Same files as the Go app. use crate::Result; use crate::models::Time; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fs::File; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::time::Instant; pub const EVENT_OUTPUT: &str = "o"; pub const EVENT_INPUT: &str = "i"; /// Bounds a single active recording file. pub const MAX_RECORDING_BYTES: u64 = 250 * 1024 * 1024; /// Bounds the file size loaded for playback. pub const MAX_PLAYBACK_BYTES: u64 = 100 * 1024 * 1024; /// Bounds one asciicast line during playback parsing. pub const MAX_PLAYBACK_LINE_BYTES: usize = 8 * 1024 * 1024; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[serde(default)] pub struct Header { pub version: i64, pub width: i64, pub height: i64, pub timestamp: i64, #[serde(skip_serializing_if = "String::is_empty")] pub title: String, #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub env: BTreeMap, } /// One recorded I/O event; `kind` is `"o"` or `"i"` (other types are kept as read). #[derive(Clone, Debug, PartialEq)] pub struct Event { pub elapsed: f64, pub kind: String, pub data: String, } #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct RecordingMeta { pub id: String, pub session_id: String, pub title: String, pub width: i64, pub height: i64, /// seconds pub duration: f64, /// file size in bytes pub size: u64, pub created_at: Time, pub file_path: PathBuf, } #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StorageInfo { pub count: usize, pub total_bytes: u64, #[serde(skip_serializing_if = "Option::is_none")] pub oldest_created_at: Option