coven/
library_dir.rs

1use std::ops::Deref;
2use std::path::{Path, PathBuf};
3use tracing::info;
4
5use crate::config::{Config, ConfigError};
6
7/// Typed wrapper for a library directory path.
8///
9/// Centralizes the on-disk layout so callers use methods instead of
10/// ad-hoc `path.join("images")` etc.
11#[derive(Clone, Debug)]
12pub struct LibraryDir {
13    path: PathBuf,
14}
15
16impl LibraryDir {
17    pub fn new(path: impl Into<PathBuf>) -> Self {
18        Self { path: path.into() }
19    }
20
21    pub fn db_path(&self) -> PathBuf {
22        self.path.join("library.db")
23    }
24
25    pub fn config_path(&self) -> PathBuf {
26        self.path.join("config.yaml")
27    }
28
29    pub fn images_dir(&self) -> PathBuf {
30        self.path.join("images")
31    }
32
33    /// Content-addressed relative path `{prefix}/{ab}/{cd}/{id}`, partitioning by
34    /// the first two byte-pairs of the dash-stripped id. The single home for the
35    /// partition scheme — shared by the local blob store and the cloud layout.
36    pub fn hashed_path(prefix: &str, id: &str) -> String {
37        let hex = id.replace('-', "");
38        format!("{prefix}/{}/{}/{id}", &hex[..2], &hex[2..4])
39    }
40
41    /// Hash-based image path: `images/{ab}/{cd}/{id}`
42    pub fn image_path(&self, id: &str) -> PathBuf {
43        self.path.join(Self::hashed_path("images", id))
44    }
45
46    pub fn torrents_dir(&self) -> PathBuf {
47        self.path.join("torrents")
48    }
49
50    /// Hash-based torrent file path: `torrents/{ab}/{cd}/{id}`
51    pub fn torrent_file_path(&self, torrent_id: &str) -> PathBuf {
52        self.path.join(Self::hashed_path("torrents", torrent_id))
53    }
54
55    pub fn storage_dir(&self) -> PathBuf {
56        self.path.join("storage")
57    }
58
59    /// Hash-based storage path: `storage/{ab}/{cd}/{file_id}`
60    pub fn storage_file_path(&self, file_id: &str) -> PathBuf {
61        self.path.join(Self::hashed_path("storage", file_id))
62    }
63
64    pub fn pending_deletions_path(&self) -> PathBuf {
65        self.path.join("pending_deletions.json")
66    }
67
68    pub fn playback_state_path(&self) -> PathBuf {
69        self.path.join("playback_state.json")
70    }
71
72    /// All asset directories that should be synced/created.
73    pub fn asset_dirs(&self) -> Vec<PathBuf> {
74        vec![self.images_dir(), self.storage_dir(), self.torrents_dir()]
75    }
76
77    /// Create a library directory, generate a device_id, and save config.yaml.
78    ///
79    /// The caller is responsible for encryption key setup and calling
80    /// `Config::save_active_library()` afterward.
81    pub fn create(
82        bae_dir: &Path,
83        library_id: String,
84        library_name: String,
85        ids: &dyn crate::id_provider::IdProvider,
86    ) -> Result<Config, ConfigError> {
87        let library_dir = LibraryDir::new(bae_dir.join("libraries").join(&library_id));
88        std::fs::create_dir_all(&*library_dir)?;
89
90        let device_id = ids.new_id();
91        let config = Config::with_defaults(library_id, device_id, library_dir, library_name);
92        config.save_to_config_yaml()?;
93
94        info!("Created library at {}", config.library_dir.display());
95        Ok(config)
96    }
97}
98
99impl Deref for LibraryDir {
100    type Target = Path;
101
102    fn deref(&self) -> &Path {
103        &self.path
104    }
105}
106
107impl AsRef<Path> for LibraryDir {
108    fn as_ref(&self) -> &Path {
109        &self.path
110    }
111}
112
113impl From<PathBuf> for LibraryDir {
114    fn from(path: PathBuf) -> Self {
115        Self { path }
116    }
117}