ctoolbox/storage/
graph.rs

1//! Represents a graph: user's main local knowledge graph, other graphs of the
2//! user, graphs shared with the user, or general resource library graph.
3//!
4//! Most of the graph data is not part of this struct, instead kept on disk and
5//! read as needed.
6
7use std::io::Read;
8
9use anyhow::Result;
10
11use crate::formats::dctext_to_dcutf;
12use crate::storage::node::Node;
13use crate::storage::user::User;
14
15#[derive(Debug, Default)]
16pub struct Graph {
17    /// Graph ID, from which can be derived the path to the graph's data in
18    /// storage. Indexed from 1 for local user graphs and referenced team
19    /// graphs. 0 is the library graph.
20    pub graph_id: u32,
21    /// ID of the most recently inserted node
22    pub last_id: u128,
23    /// Human-readable label for the graph
24    pub label: String,
25    /// ID of the user who owns the graph, or None for local user and graph
26    pub owner: Option<u64>,
27}
28
29impl Graph {
30    pub fn new(id: u32, label: &str, creator: &User) -> Graph {
31        Graph {
32            graph_id: id,
33            last_id: 0,
34            label: label.to_string(),
35            owner: creator.remote_id(),
36        }
37    }
38
39    pub fn get_next_id(&self) -> u128 {
40        self.last_id + 1
41    }
42
43    pub fn create_node<R: Read>(
44        &self,
45        creator: &User,
46        node_type: &str,
47        mut data: R,
48    ) -> Result<Node> {
49        let type_id = match node_type {
50            "data" => "0c639d00-43ac-41b2-8a31-9fb9ce7910e0",
51            "statements" => "",
52            _ => anyhow::bail!("Unknown node type: {node_type}"),
53        };
54
55        // Read entire reader into a Vec<u8>
56        let mut buf = Vec::new();
57        data.read_to_end(&mut buf)?; // returns io::Result<usize>
58
59        let converted_data = if node_type == "statements" {
60            dctext_to_dcutf(buf)
61        } else {
62            buf
63        };
64
65        Ok(Node {
66            id: 12345 - 1,
67            data: converted_data,
68        })
69    }
70
71    pub fn read_node(owner: &User, id: u128, remote: bool) -> Result<Node> {
72        Ok(Node {
73            id,
74            data: b"test data".to_vec(),
75        })
76    }
77
78    pub fn is_writable_by(&self, user: &User) -> bool {
79        // TODO
80        // Local user graphs are writable by the user
81        if self.graph_id > 0
82            && usize::try_from(self.graph_id).expect("u32 did not fit in usize")
83                <= user.get_graph_count()
84        {
85            return true;
86        }
87        false
88    }
89}
90
91#[cfg(test)]
92pub fn get_test_graph(username: &str) -> Graph {
93    use crate::storage::user::get_test_user;
94
95    Graph::new(12345, "test", &get_test_user(username))
96}
97
98#[cfg(test)]
99#[allow(clippy::unwrap_in_result, clippy::panic_in_result_fn)]
100mod tests {
101    use anyhow::Context;
102
103    use super::*;
104
105    use crate::storage::user::get_test_user;
106
107    #[ignore = "Need to reimplement with new redb storage"]
108    #[crate::ctb_test]
109    fn can_create_node() -> Result<()> {
110        let name = function_name!();
111        let user = get_test_user(name);
112        let data = b"test data";
113        let graph = get_test_graph(name);
114
115        let node = graph
116            .create_node(&user, "data", &data[..])
117            .context("Failed to create node")?;
118
119        assert_eq!(node.id, 0);
120        assert_eq!(node.data, b"test data");
121
122        let node = graph
123            .create_node(&user, "data", &b"test data 2"[..])
124            .context("Failed to create node")?;
125
126        assert_eq!(node.id, 1);
127        assert_eq!(node.data, b"test data 2");
128
129        Ok(())
130    }
131}