ant_bootstrap/cache_store/
cache_data_v1.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 crate::Error;
10
11use atomic_write_file::AtomicWriteFile;
12use libp2p::{Multiaddr, PeerId};
13use serde::{Deserialize, Serialize};
14use std::{
15    collections::VecDeque,
16    fs::{self, OpenOptions},
17    io::{Read, Write},
18    path::{Path, PathBuf},
19    time::SystemTime,
20};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CacheData {
24    pub peers: VecDeque<(PeerId, VecDeque<Multiaddr>)>,
25    pub last_updated: SystemTime,
26    pub network_version: String,
27    pub cache_version: String,
28}
29
30impl CacheData {
31    /// The version of the cache data format
32    /// This has to be bumped whenever the cache data format changes to ensure compatibility.
33    pub const CACHE_DATA_VERSION: u32 = 1;
34
35    /// Sync the self cache with another cache. This would just add the 'other' state to self.
36    pub fn sync(&mut self, other: &CacheData, max_addrs_per_peer: usize, max_peers: usize) {
37        for (other_peer, other_addrs) in other.peers.iter() {
38            if other_addrs.is_empty() {
39                continue;
40            }
41            for (peer, addrs) in self.peers.iter_mut() {
42                if peer == other_peer {
43                    for addr in other_addrs.iter() {
44                        if !addrs.contains(addr) {
45                            addrs.push_back(addr.clone());
46                        }
47                    }
48                    while addrs.len() > max_addrs_per_peer {
49                        addrs.pop_front();
50                    }
51                    break;
52                }
53            }
54
55            self.peers.push_back((*other_peer, other_addrs.clone()));
56
57            while self.peers.len() > max_peers {
58                self.peers.pop_front();
59            }
60        }
61
62        self.last_updated = SystemTime::now();
63    }
64
65    /// Add a peer to the cachse data
66    pub fn add_peer<'a>(
67        &mut self,
68        peer_id: PeerId,
69        addrs: impl Iterator<Item = &'a Multiaddr>,
70        max_addrs_per_peer: usize,
71        max_peers: usize,
72    ) {
73        if let Some((_, present_addrs)) = self.peers.iter_mut().find(|(id, _)| id == &peer_id) {
74            for addr in addrs {
75                if !present_addrs.contains(addr) {
76                    present_addrs.push_back(addr.clone());
77                }
78            }
79            while present_addrs.len() > max_addrs_per_peer {
80                present_addrs.pop_front();
81            }
82        } else {
83            self.peers.push_back((
84                peer_id,
85                addrs
86                    .into_iter()
87                    .take(max_addrs_per_peer)
88                    .cloned()
89                    .collect(),
90            ));
91        }
92
93        while self.peers.len() > max_peers {
94            self.peers.pop_front();
95        }
96    }
97
98    pub fn get_all_addrs(&self) -> impl Iterator<Item = &Multiaddr> {
99        self.peers
100            .iter()
101            .flat_map(|(_, bootstrap_addresses)| bootstrap_addresses.iter().next())
102    }
103
104    pub fn read_from_file(cache_dir: &Path, file_name: &str) -> Result<Self, Error> {
105        let file_path = Self::cache_file_path(cache_dir, file_name);
106        // Try to open the file with read permissions
107        let mut file = OpenOptions::new()
108            .read(true)
109            .open(&file_path)
110            .inspect_err(|err| warn!("Failed to open cache file at {file_path:?} : {err}",))?;
111
112        // Read the file contents
113        let mut contents = String::new();
114        file.read_to_string(&mut contents).inspect_err(|err| {
115            warn!("Failed to read cache file: {err}");
116        })?;
117
118        // Parse the cache data
119        let data = serde_json::from_str::<Self>(&contents).map_err(|err| {
120            warn!("Failed to parse cache data: {err}");
121            Error::FailedToParseCacheData
122        })?;
123
124        Ok(data)
125    }
126
127    pub fn write_to_file(&self, cache_dir: &Path, file_name: &str) -> Result<(), Error> {
128        let file_path = Self::cache_file_path(cache_dir, file_name);
129
130        // Create parent directory if it doesn't exist
131        if let Some(parent) = file_path.parent() {
132            fs::create_dir_all(parent)?;
133        }
134
135        let mut file = AtomicWriteFile::options()
136            .open(&file_path)
137            .inspect_err(|err| {
138                error!("Failed to open cache file at {file_path:?} using AtomicWriteFile: {err}");
139            })?;
140
141        let data = serde_json::to_string_pretty(&self).inspect_err(|err| {
142            error!("Failed to serialize cache data: {err}");
143        })?;
144        writeln!(file, "{data}")?;
145        file.commit().inspect_err(|err| {
146            error!("Failed to commit atomic write: {err}");
147        })?;
148
149        info!("Cache written to disk: {:?}", file_path);
150
151        Ok(())
152    }
153
154    pub fn cache_file_path(cache_dir: &Path, file_name: &str) -> PathBuf {
155        cache_dir
156            .join(format!("version_{}", Self::CACHE_DATA_VERSION))
157            .join(file_name)
158    }
159}
160
161impl Default for CacheData {
162    fn default() -> Self {
163        Self {
164            peers: Default::default(),
165            last_updated: SystemTime::now(),
166            network_version: crate::get_network_version(),
167            cache_version: Self::CACHE_DATA_VERSION.to_string(),
168        }
169    }
170}