ant_service_management/
registry.rs

1// Copyright (C) 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use crate::error::{Error, Result};
10use crate::{DaemonServiceData, NatDetectionStatus, NodeServiceData};
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13use std::{
14    io::{Read, Write},
15    path::{Path, PathBuf},
16};
17use tokio::sync::RwLock;
18
19/// Used to manage the NodeRegistry data and allows us to share the data across multiple threads.
20///
21/// Can be cloned freely.
22#[derive(Clone, Debug)]
23#[allow(clippy::type_complexity)]
24pub struct NodeRegistryManager {
25    pub daemon: Arc<RwLock<Option<Arc<RwLock<DaemonServiceData>>>>>,
26    pub environment_variables: Arc<RwLock<Option<Vec<(String, String)>>>>,
27    pub nat_status: Arc<RwLock<Option<NatDetectionStatus>>>,
28    pub nodes: Arc<RwLock<Vec<Arc<RwLock<NodeServiceData>>>>>,
29    pub save_path: PathBuf,
30}
31
32impl From<NodeRegistry> for NodeRegistryManager {
33    fn from(registry: NodeRegistry) -> Self {
34        NodeRegistryManager {
35            daemon: Arc::new(RwLock::new(
36                registry.daemon.map(|daemon| Arc::new(RwLock::new(daemon))),
37            )),
38            environment_variables: Arc::new(RwLock::new(registry.environment_variables)),
39            nat_status: Arc::new(RwLock::new(registry.nat_status)),
40            nodes: Arc::new(RwLock::new(
41                registry
42                    .nodes
43                    .into_iter()
44                    .map(|node| Arc::new(RwLock::new(node)))
45                    .collect(),
46            )),
47            save_path: registry.save_path,
48        }
49    }
50}
51
52impl NodeRegistryManager {
53    /// Creates a new `NodeRegistryManager` with the specified save path.
54    ///
55    /// This is primarily used for testing purposes.
56    pub fn empty(save_path: PathBuf) -> Self {
57        NodeRegistryManager {
58            daemon: Arc::new(RwLock::new(None)),
59            environment_variables: Arc::new(RwLock::new(None)),
60            nat_status: Arc::new(RwLock::new(None)),
61            nodes: Arc::new(RwLock::new(Vec::new())),
62            save_path,
63        }
64    }
65
66    /// Loads the node registry from the specified path.
67    /// If the file does not exist, it returns a default `NodeRegistryManager` with an empty state.
68    #[allow(clippy::unused_async)]
69    pub async fn load(path: &Path) -> Result<Self> {
70        let registry = NodeRegistry::load(path)?;
71        let manager = NodeRegistryManager::from(registry);
72
73        Ok(manager)
74    }
75
76    /// Saves the current state of the node registry to the specified path.
77    pub async fn save(&self) -> Result<()> {
78        let registry = self.to_registry().await;
79        registry.save()?;
80        Ok(())
81    }
82
83    /// Converts the current state of the `NodeRegistryManager` to a `NodeRegistry`.
84    async fn to_registry(&self) -> NodeRegistry {
85        let nodes = self.get_node_service_data().await;
86        let mut daemon = None;
87        {
88            if let Some(d) = self.daemon.read().await.as_ref() {
89                daemon = Some(d.read().await.clone());
90            }
91        }
92        NodeRegistry {
93            daemon,
94            environment_variables: self.environment_variables.read().await.clone(),
95            nat_status: self.nat_status.read().await.clone(),
96            nodes,
97            save_path: self.save_path.clone(),
98        }
99    }
100
101    /// Converts the current state of the `NodeRegistryManager` to a `StatusSummary`.
102    pub async fn to_status_summary(&self) -> StatusSummary {
103        let registry = self.to_registry().await;
104        registry.to_status_summary()
105    }
106
107    /// Inserts a new NodeServiceData into the registry.
108    pub async fn push_node(&self, node: NodeServiceData) {
109        let mut nodes = self.nodes.write().await;
110        nodes.push(Arc::new(RwLock::new(node)));
111    }
112
113    /// Inserts the DaemonServiceData into the registry.
114    pub async fn insert_daemon(&self, daemon: DaemonServiceData) {
115        let mut daemon_lock = self.daemon.write().await;
116        *daemon_lock = Some(Arc::new(RwLock::new(daemon)));
117    }
118
119    pub async fn get_node_service_data(&self) -> Vec<NodeServiceData> {
120        let mut node_services = Vec::new();
121        for node in self.nodes.read().await.iter() {
122            let node = node.read().await;
123            node_services.push(node.clone());
124        }
125        node_services
126    }
127}
128
129/// The struct that is written to the fs.
130#[derive(Clone, Debug, Serialize, Deserialize)]
131struct NodeRegistry {
132    daemon: Option<DaemonServiceData>,
133    environment_variables: Option<Vec<(String, String)>>,
134    nat_status: Option<NatDetectionStatus>,
135    nodes: Vec<NodeServiceData>,
136    save_path: PathBuf,
137}
138
139#[derive(Clone, Debug, Serialize, Deserialize)]
140pub struct StatusSummary {
141    pub nodes: Vec<NodeServiceData>,
142    pub daemon: Option<DaemonServiceData>,
143}
144
145impl NodeRegistry {
146    fn save(&self) -> Result<()> {
147        debug!(
148            "Saving node registry to {}",
149            self.save_path.to_string_lossy()
150        );
151        let path = Path::new(&self.save_path);
152        if let Some(parent) = path.parent() {
153            std::fs::create_dir_all(parent).inspect_err(|err| {
154                error!("Error creating node registry parent {parent:?}: {err:?}")
155            })?;
156        }
157
158        let json = serde_json::to_string(self)?;
159        let mut file = std::fs::File::create(self.save_path.clone())
160            .inspect_err(|err| error!("Error creating node registry file: {err:?}"))?;
161        file.write_all(json.as_bytes())
162            .inspect_err(|err| error!("Error writing to node registry: {err:?}"))?;
163
164        Ok(())
165    }
166
167    fn load(path: &Path) -> Result<Self> {
168        if !path.exists() {
169            debug!("Loading default node registry as {path:?} does not exist");
170            return Ok(NodeRegistry {
171                daemon: None,
172                environment_variables: None,
173                nat_status: None,
174                nodes: vec![],
175                save_path: path.to_path_buf(),
176            });
177        }
178        debug!("Loading node registry from {}", path.to_string_lossy());
179
180        let mut file = std::fs::File::open(path)
181            .inspect_err(|err| error!("Error opening node registry: {err:?}"))?;
182
183        let mut contents = String::new();
184        file.read_to_string(&mut contents)
185            .inspect_err(|err| error!("Error reading node registry: {err:?}"))?;
186
187        // It's possible for the file to be empty if the user runs a `status` command before any
188        // services were added.
189        if contents.is_empty() {
190            return Ok(NodeRegistry {
191                daemon: None,
192                environment_variables: None,
193                nat_status: None,
194                nodes: vec![],
195                save_path: path.to_path_buf(),
196            });
197        }
198
199        Self::from_json(&contents)
200    }
201
202    fn from_json(json: &str) -> Result<Self> {
203        let registry = serde_json::from_str(json)
204            .inspect_err(|err| error!("Error deserializing node registry: {err:?}"))?;
205        Ok(registry)
206    }
207
208    fn to_status_summary(&self) -> StatusSummary {
209        StatusSummary {
210            nodes: self.nodes.clone(),
211            daemon: self.daemon.clone(),
212        }
213    }
214}
215
216pub fn get_local_node_registry_path() -> Result<PathBuf> {
217    let path = dirs_next::data_dir()
218        .ok_or_else(|| {
219            error!("Failed to get data_dir");
220            Error::UserDataDirectoryNotObtainable
221        })?
222        .join("autonomi")
223        .join("local_node_registry.json");
224    if let Some(parent) = path.parent() {
225        std::fs::create_dir_all(parent)
226            .inspect_err(|err| error!("Error creating node registry parent {parent:?}: {err:?}"))?;
227    }
228    Ok(path)
229}