Skip to main content

rig_harness/
plugin_data.rs

1//! Where a plugin keeps files of its own, by how long they live:
2//! [`PluginData`], in every run. A plugin names its files under its own
3//! name, such as `vim/registers.json`.
4//!
5//! ```
6//! use rig_harness::prelude::*;
7//!
8//! fn save(data: Res<PluginData>) -> std::io::Result<()> {
9//!     // Kept with the session.
10//!     data.session.write_file("vim/registers.json", b"{}")?;
11//!     // This project's, whichever session works in it.
12//!     data.project.write_file("vim/marks.json", b"{}")?;
13//!     // Every session's.
14//!     data.user.write_file("vim/mode.json", b"\"normal\"")
15//! }
16//! ```
17
18use std::path::{Path, PathBuf};
19use std::sync::Arc;
20
21use bevy_ecs::prelude::*;
22use rig_core::tool::content_hash;
23use rig_harness_protocol::{Home, SessionDir};
24use rig_memory::{DirFiles, Files, MemoryStore, PluginFiles, SessionError};
25
26use crate::host::session::StoreOpener;
27
28/// Where plugins keep their files: per session, per project and per
29/// user. Not in the project's own directory: a plugin's data is the
30/// user's, never committed and never read from a checkout someone else
31/// made. Inserted once every plugin is built, in every run, unless the
32/// app has one: a run without a saved session keeps `session` in memory.
33#[derive(Resource, Clone)]
34pub struct PluginData {
35    /// This session's: `plugins/` in the session's store, which a delete
36    /// removes.
37    pub session: Arc<dyn Files>,
38    /// This project's, for this user, whichever session works in it:
39    /// `$RIG_HOME/projects/<name>-<key>/`, the key from the project's
40    /// canonical path, so every spelling of it shares one.
41    pub project: Arc<dyn Files>,
42    /// Across sessions and projects: `$RIG_HOME/data/`.
43    pub user: Arc<dyn Files>,
44}
45
46impl PluginData {
47    /// The places of a session kept in `session` (in memory with `None`),
48    /// working on the project at `root` in the workspace on `host` (`None`
49    /// for this machine), under `home`.
50    pub(crate) fn new(
51        home: &Home,
52        session: Option<Arc<dyn Files>>,
53        root: &Path,
54        host: Option<&str>,
55    ) -> Self {
56        let session = session.unwrap_or_else(|| Arc::new(MemoryStore::default()));
57        Self {
58            session: Arc::new(PluginFiles(session)),
59            project: Arc::new(DirFiles::new(project_dir(home, root, host))),
60            user: Arc::new(DirFiles::new(home.data())),
61        }
62    }
63}
64
65impl PluginData {
66    /// The plugin files of the saved session in `dir`, kept in the store
67    /// `stores` opens for it (`plugins/` of that store): what
68    /// [`PluginData::session`] is in a run of that session.
69    ///
70    /// # Errors
71    ///
72    /// When its store does not open.
73    pub fn of(stores: &StoreOpener, dir: &SessionDir) -> Result<Arc<dyn Files>, SessionError> {
74        let store: Arc<dyn Files> = stores.open(dir)?;
75        Ok(Arc::new(PluginFiles(store)))
76    }
77}
78
79/// The directory of `home` keeping plugins' files for the project at
80/// `root` on `host`: its name and the first 8 hex digits of the SHA-256
81/// of where it is, canonical on this machine.
82fn project_dir(home: &Home, root: &Path, host: Option<&str>) -> PathBuf {
83    let root = match host {
84        None => root.canonicalize().unwrap_or_else(|_| root.to_owned()),
85        Some(_) => root.to_owned(),
86    };
87    let mut place = host.map(|host| format!("{host}:")).unwrap_or_default();
88    place.push_str(&root.to_string_lossy());
89    let hash = content_hash(place.as_bytes()).0;
90    let name = root
91        .file_name()
92        .map(|name| name.to_string_lossy().into_owned())
93        .unwrap_or_default();
94    let key = hash.get(..8).unwrap_or(&hash);
95    home.projects().join(format!("{name}-{key}"))
96}
97
98#[cfg(test)]
99mod tests;