ant_bootstrap/cache_store/
cache_data_v0.rs

1// Copyright 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 std::{
10    fs::{self, OpenOptions},
11    io::{Read, Write},
12    path::{Path, PathBuf},
13    time::SystemTime,
14};
15
16use atomic_write_file::AtomicWriteFile;
17use libp2p::{Multiaddr, PeerId};
18use serde::{Deserialize, Serialize};
19
20use crate::Error;
21
22use super::cache_data_v1;
23
24/// A addr that can be used for bootstrapping into the network
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct BootstrapAddr {
27    /// The multiaddress of the peer
28    pub addr: Multiaddr,
29    /// The number of successful connections to this address
30    pub success_count: u32,
31    /// The number of failed connection attempts to this address
32    pub failure_count: u32,
33    /// The last time this address was successfully contacted
34    pub last_seen: SystemTime,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38/// Set of addresses for a particular PeerId
39pub struct BootstrapAddresses(pub Vec<BootstrapAddr>);
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct CacheData {
43    pub peers: std::collections::HashMap<PeerId, BootstrapAddresses>,
44    pub last_updated: SystemTime,
45    pub network_version: String,
46}
47
48impl From<&cache_data_v1::CacheData> for CacheData {
49    fn from(data: &cache_data_v1::CacheData) -> Self {
50        let mut peers = std::collections::HashMap::new();
51        for (peer_id, addrs) in &data.peers {
52            let addrs = addrs
53                .iter()
54                .map(|addr| BootstrapAddr {
55                    addr: addr.clone(),
56                    success_count: 0,
57                    failure_count: 0,
58                    last_seen: SystemTime::now(),
59                })
60                .collect();
61            peers.insert(*peer_id, BootstrapAddresses(addrs));
62        }
63
64        Self {
65            peers,
66            last_updated: data.last_updated,
67            network_version: data.network_version.clone(),
68        }
69    }
70}
71
72impl From<CacheData> for cache_data_v1::CacheData {
73    fn from(val: CacheData) -> Self {
74        let peers = val
75            .peers
76            .into_iter()
77            .map(|(peer_id, addrs)| {
78                let addrs = addrs.0.into_iter().map(|addr| addr.addr).collect();
79                (peer_id, addrs)
80            })
81            .collect();
82
83        cache_data_v1::CacheData {
84            peers,
85            last_updated: val.last_updated,
86            network_version: val.network_version,
87            cache_version: cache_data_v1::CacheData::CACHE_DATA_VERSION.to_string(),
88        }
89    }
90}
91
92impl CacheData {
93    pub fn read_from_file(cache_dir: &Path, file_name: &str) -> Result<Self, Error> {
94        let file_path = Self::cache_file_path(cache_dir, file_name);
95        // Try to open the file with read permissions
96        let mut file = OpenOptions::new()
97            .read(true)
98            .open(&file_path)
99            .inspect_err(|err| warn!("Failed to open cache file at {file_path:?} : {err}",))?;
100
101        // Read the file contents
102        let mut contents = String::new();
103        file.read_to_string(&mut contents).inspect_err(|err| {
104            warn!("Failed to read cache file: {err}");
105        })?;
106
107        // Parse the cache data
108        let data = serde_json::from_str::<Self>(&contents).map_err(|err| {
109            warn!("Failed to parse cache data: {err}");
110            Error::FailedToParseCacheData
111        })?;
112
113        Ok(data)
114    }
115
116    pub fn write_to_file(&self, cache_dir: &Path, file_name: &str) -> Result<(), Error> {
117        let file_path = Self::cache_file_path(cache_dir, file_name);
118
119        // Create parent directory if it doesn't exist
120        if let Some(parent) = file_path.parent() {
121            fs::create_dir_all(parent)?;
122        }
123
124        let mut file = AtomicWriteFile::options()
125            .open(&file_path)
126            .inspect_err(|err| {
127                error!("Failed to open cache file at {file_path:?} using AtomicWriteFile: {err}");
128            })?;
129
130        let data = serde_json::to_string_pretty(&self).inspect_err(|err| {
131            error!("Failed to serialize cache data: {err}");
132        })?;
133        writeln!(file, "{data}")?;
134        file.commit().inspect_err(|err| {
135            error!("Failed to commit atomic write: {err}");
136        })?;
137
138        info!("Cache written to disk: {:?}", file_path);
139
140        Ok(())
141    }
142
143    pub fn cache_file_path(cache_dir: &Path, file_name: &str) -> PathBuf {
144        cache_dir.join(file_name)
145    }
146}