Skip to main content

squigit_storage/threads/
mod.rs

1// Copyright 2026 a7mddra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Thread storage manager and thread-local persisted state.
5
6use std::fs;
7use std::path::Path;
8use std::path::PathBuf;
9
10use crate::error::{Result, StorageError};
11
12mod index;
13mod lifecycle;
14mod ocr;
15mod paths;
16pub mod types;
17
18pub use types::{
19    default_ocr_annotations, AttachmentManifest, AttachmentManifestEntry, ContextWindow,
20    MessageAttachment, OcrAnnotationEntry, OcrAnnotations, OcrModelAnnotation, OcrRegion,
21    SideChatData, SideChatMetadata, ThreadData, ThreadMessage, ThreadMetadata, WorkspaceMetadata,
22    DEFAULT_SIDE_CHAT_TITLE, DEFAULT_THREAD_TITLE, EMPTY_STATE_ASSET_ID,
23};
24
25pub(crate) fn atomic_write(path: &Path, contents: &[u8]) -> Result<()> {
26    let file_name = path
27        .file_name()
28        .and_then(|value| value.to_str())
29        .unwrap_or("data");
30    let temporary = path.with_file_name(format!(".{file_name}.tmp-{}", uuid::Uuid::new_v4()));
31    fs::write(&temporary, contents)?;
32    fs::rename(temporary, path)?;
33    Ok(())
34}
35
36/// Main storage manager for threads and content-addressed objects.
37pub struct ThreadStorage {
38    /// Base directory for all thread storage.
39    pub(crate) base_dir: PathBuf,
40    /// Directory for content-addressed objects.
41    pub(crate) objects_dir: PathBuf,
42    /// Path to the thread index file.
43    pub(crate) index_path: PathBuf,
44}
45
46impl ThreadStorage {
47    pub fn with_config_root(config_root: PathBuf) -> Result<Self> {
48        let base_dir = config_root.join("threads");
49        let objects_dir = config_root.join("objects");
50        let index_path = base_dir.join("index.json");
51        fs::create_dir_all(&base_dir)?;
52        fs::create_dir_all(&objects_dir)?;
53        Ok(Self {
54            base_dir,
55            objects_dir,
56            index_path,
57        })
58    }
59
60    /// Create a new storage manager using the default global thread location.
61    pub fn new() -> Result<Self> {
62        let config_root = crate::paths::base_config_dir().ok_or(StorageError::NoDataDir)?;
63        Self::with_config_root(config_root)
64    }
65
66    /// Get the base storage directory path.
67    pub fn base_dir(&self) -> &PathBuf {
68        &self.base_dir
69    }
70
71    /// Get the objects directory path.
72    pub fn objects_dir(&self) -> &PathBuf {
73        &self.objects_dir
74    }
75
76    pub(super) fn thread_dir(&self, thread_id: &str) -> PathBuf {
77        self.base_dir.join(thread_id)
78    }
79}