Skip to main content

mj_controller/database/
mounts.rs

1use super::*;
2
3/// Atomically apply the controller's MRU policy for newly used mount sources.
4pub fn remember_mount_sources(host: &str, mounts: &[AdditionalMount]) -> Result<()> {
5    if mounts.is_empty() {
6        return Ok(());
7    }
8    let host = host.to_owned();
9    let sources = mounts
10        .iter()
11        .map(|mount| mount.source.clone())
12        .collect::<Vec<_>>();
13    submit_database_write("remember_mount_sources", move |_| {
14        remember_sources(&database_path(), &host, sources)
15    })
16}
17
18pub fn replace_mount_history(host: &str, sources: &[PathBuf]) -> Result<()> {
19    let host = host.to_owned();
20    let sources = sources.to_vec();
21    submit_database_write("replace_mount_history", move |_| {
22        replace_mount_history_in(&database_path(), &host, &sources)
23    })
24}
25
26pub(super) fn replace_mount_history_in(path: &Path, host: &str, sources: &[PathBuf]) -> Result<()> {
27    let mut connection = open(path)?;
28    let tx = connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
29    write_mount_history(&tx, host, sources)?;
30    tx.commit()?;
31    Ok(())
32}
33
34pub(super) fn write_mount_history(
35    tx: &Transaction<'_>,
36    host: &str,
37    sources: &[PathBuf],
38) -> Result<()> {
39    tx.execute("DELETE FROM mount_history WHERE host = ?1", [host])?;
40    let mut written = Vec::new();
41    for source in sources.iter().take(20) {
42        if written.contains(source) {
43            continue;
44        }
45        tx.execute(
46            "INSERT INTO mount_history(host, source, ordinal) VALUES (?1, ?2, ?3)",
47            params![host, path_to_blob(source), written.len() as i64],
48        )?;
49        written.push(source.clone());
50    }
51    Ok(())
52}
53
54pub(super) fn write_host_container_size(
55    tx: &Transaction<'_>,
56    host: &str,
57    size: HostContainerSize,
58) -> Result<()> {
59    ensure!(!host.trim().is_empty(), "container size host is empty");
60    let cpus = i64::try_from(size.cpus).context("container CPU count exceeds SQLite range")?;
61    let memory =
62        i64::try_from(size.memory_bytes).context("container memory exceeds SQLite range")?;
63    ensure!(
64        cpus > 0 && memory > 0,
65        "container size values must be positive"
66    );
67    tx.execute(
68        "INSERT INTO host_container_sizes(host, cpus, memory_bytes)
69         VALUES (?1, ?2, ?3)
70         ON CONFLICT(host) DO UPDATE SET cpus = excluded.cpus, memory_bytes = excluded.memory_bytes",
71        params![host, cpus, memory],
72    )?;
73    Ok(())
74}
75
76pub fn remember_project_directory(host: &str, directory: &Path) -> Result<()> {
77    let host = format!("project:{host}");
78    let directory = directory.to_path_buf();
79    submit_database_write("remember_project_directory", move |_| {
80        remember_sources(&database_path(), &host, std::iter::once(directory))
81    })
82}
83
84pub(super) fn remember_sources(
85    path: &Path,
86    host: &str,
87    new_sources: impl IntoIterator<Item = PathBuf>,
88) -> Result<()> {
89    let mut connection = open(path)?;
90    let tx = connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
91    let mut sources = {
92        let mut statement =
93            tx.prepare("SELECT source FROM mount_history WHERE host = ?1 ORDER BY ordinal")?;
94        statement
95            .query_map([host], |row| Ok(blob_to_path(row.get_ref(0)?.as_blob()?)))?
96            .collect::<rusqlite::Result<Vec<_>>>()?
97    };
98    let additions = new_sources.into_iter().collect::<Vec<_>>();
99    for source in additions.iter().rev() {
100        sources.retain(|existing| existing != source);
101        sources.insert(0, source.clone());
102    }
103    sources.truncate(20);
104    write_mount_history(&tx, host, &sources)?;
105    tx.commit()?;
106    Ok(())
107}