subrpc_core/
local_data.rs1use anyhow::Result;
2use chrono::{DateTime, Local};
3use log::*;
4use serde::{Deserialize, Serialize};
5use std::{
6 collections::{HashMap, HashSet},
7 fs::{self, File},
8 io::{Read, Write},
9 path::{Path, PathBuf},
10};
11
12use crate::{endpoint::Endpoint, Registry};
13
14#[derive(PartialEq, Debug, Deserialize, Serialize)]
20pub struct LocalData {
21 pub file: PathBuf,
23
24 pub registries: HashMap<String, Registry>,
26
27 pub last_update: Option<DateTime<Local>>,
29}
30
31impl LocalData {
32 pub fn get_default_file() -> PathBuf {
33 let home = dirs::home_dir().expect("Failed fetching home dir");
34 let dir = Path::new(&home).join(".subrpc");
35 let _ = fs::create_dir_all(&dir);
36 dir.join("data.json")
37 }
38
39 pub fn initialized(&self) -> bool {
41 self.file.exists()
42 }
43
44 pub fn init(file: &Path, force: bool) -> Result<Self> {
48 debug!("Initializing local data in {} with force: {:?}", file.display(), force);
49
50 let data = Self { file: file.to_path_buf(), ..Default::default() };
51
52 if file.exists() && !force {
53 info!("File already exists: {}", file.display());
54 data.load()
55 } else {
56 data.save()
57 }
58 }
59
60 pub fn load(self) -> Result<Self> {
62 debug!("Loading data from {}", self.file.display());
63 let mut fs = File::open(self.file)?;
64 let mut s = String::new();
65 fs.read_to_string(&mut s)?;
66 serde_json::from_str(&s).map_err(anyhow::Error::msg)
67 }
68
69 pub fn refresh(mut self) -> Self {
72 debug!("Refreshing registries");
73
74 self.registries.iter_mut().for_each(|(_registry_name, reg)| {
75 debug!(" - {} - enabled: {:?}", ®.name, ®.enabled);
76 match reg.update() {
78 Ok(_) => {
79 info!("Update of '{}' OK", reg.name);
80 }
81 Err(e) => {
82 error!("Update registry '{}' failed: {e:?}", reg.name);
84 }
85 }
86 });
87
88 self.last_update = Some(Local::now());
89 self
90 }
91
92 pub fn add_registry(mut self, registry: Registry) -> Self {
94 self.registries.insert(registry.name.clone(), registry);
95 self
96 }
97
98 pub fn save(self) -> Result<Self> {
100 debug!("Saving data to {}", self.file.display());
101 let json = serde_json::to_string_pretty(&self)?;
102 let mut fs = File::create(&self.file)?;
103 fs.write_all(json.as_bytes())?;
104 Ok(self)
105 }
106
107 pub fn get_endpoints(&self, chain: Option<&str>) -> HashSet<Endpoint> {
110 let mut endpoint_vec: HashSet<Endpoint> = HashSet::new();
111 self.registries.iter().for_each(|(_, reg)| {
112 if !reg.enabled {
113 } else {
115 reg.rpc_endpoints
116 .iter()
117 .filter(|(c, _)| {
118 if let Some(filter) = chain {
119 c.as_str().to_ascii_lowercase() == filter.to_ascii_lowercase()
120 } else {
121 true
122 }
123 })
124 .for_each(|(_, e)| {
125 let ee = e.clone();
126 endpoint_vec.extend(ee);
127 });
128 }
129 });
130 endpoint_vec
131 }
132
133 pub fn print_registries(&self) {
137 self.registries.iter().for_each(|(_name, reg)| {
139 println!("- [{}] {:?} {:?}", if reg.enabled { "X" } else { " " }, reg.name, reg.url);
140 })
141 }
142
143 pub fn print_summary(&self) {
145 self.registries.iter().for_each(|(_name, reg)| {
146 println!(
147 "- [{}] {} - {}",
148 if reg.enabled { "X" } else { " " },
149 reg.name,
150 if let Some(url) = ®.url { url } else { "n/a" }
151 );
152 println!(" rpc endpoints: {:?}", reg.rpc_endpoints.len());
153 println!(" last update: {:?}", reg.last_update);
154 })
155 }
156}
157
158impl Default for LocalData {
159 fn default() -> Self {
160 Self { file: Self::get_default_file(), registries: HashMap::new(), last_update: None }
161 }
162}
163
164#[cfg(test)]
165mod test_local_data {
166 use super::*;
167
168 #[test]
169 fn test_builder() {
170 let data = LocalData::init(&LocalData::get_default_file(), true)
171 .expect("Forced init should work")
172 .save()
173 .expect("Saving data should work")
174 .load().expect("Load works")
175 .add_registry(Registry::new("SubRPC", "https://raw.githubusercontent.com/chevdor/subrpc/master/registry/subrpc.json"))
176 .add_registry(Registry::new("SubRPC Gist", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data.json"))
177 .refresh()
178 .save().expect("Saving works");
179 println!("{data:#?}");
180 }
181
182 #[test]
183 fn test_merge() {
184 let data = LocalData::init(&LocalData::get_default_file(), true)
185 .expect("Forced init should work")
186 .add_registry(Registry::new("SubRPC Gist 1", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data.json"))
187 .add_registry(Registry::new("SubRPC Gist 2", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data2.json"))
188 .refresh()
189 .save().expect("Saving works");
190 assert_eq!(2, data.registries.len());
191 println!("{data:#?}");
192 }
193}