use anyhow::Result;
use chrono::{DateTime, Local};
use log::*;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
fs::{self, File},
io::{Read, Write},
path::{Path, PathBuf},
};
use crate::{endpoint::Endpoint, Registry};
#[derive(PartialEq, Debug, Deserialize, Serialize)]
pub struct LocalData {
pub file: PathBuf,
pub registries: HashMap<String, Registry>,
pub last_update: Option<DateTime<Local>>,
}
impl LocalData {
pub fn get_default_file() -> PathBuf {
let home = dirs::home_dir().expect("Failed fetching home dir");
let dir = Path::new(&home).join(".subrpc");
let _ = fs::create_dir_all(&dir);
dir.join("data.json")
}
pub fn initialized(&self) -> bool {
self.file.exists()
}
pub fn init(file: &Path, force: bool) -> Result<Self> {
debug!("Initializing local data in {} with force: {:?}", file.display(), force);
let data = Self { file: file.to_path_buf(), ..Default::default() };
if file.exists() && !force {
info!("File already exists: {}", file.display());
data.load()
} else {
data.save()
}
}
pub fn load(self) -> Result<Self> {
debug!("Loading data from {}", self.file.display());
let mut fs = File::open(self.file)?;
let mut s = String::new();
fs.read_to_string(&mut s)?;
serde_json::from_str(&s).map_err(anyhow::Error::msg)
}
pub fn refresh(mut self) -> Self {
debug!("Refreshing registries");
self.registries.iter_mut().for_each(|(_registry_name, reg)| {
debug!(" - {} - enabled: {:?}", ®.name, ®.enabled);
match reg.update() {
Ok(_) => {
info!("Update of '{}' OK", reg.name);
}
Err(e) => {
error!("Update registry '{}' failed: {e:?}", reg.name);
}
}
});
self.last_update = Some(Local::now());
self
}
pub fn add_registry(mut self, registry: Registry) -> Self {
self.registries.insert(registry.name.clone(), registry);
self
}
pub fn save(self) -> Result<Self> {
debug!("Saving data to {}", self.file.display());
let json = serde_json::to_string_pretty(&self)?;
let mut fs = File::create(&self.file)?;
fs.write_all(json.as_bytes())?;
Ok(self)
}
pub fn get_endpoints(&self, chain: Option<&str>) -> HashSet<Endpoint> {
let mut endpoint_vec: HashSet<Endpoint> = HashSet::new();
self.registries.iter().for_each(|(_, reg)| {
if !reg.enabled {
} else {
reg.rpc_endpoints
.iter()
.filter(|(c, _)| {
if let Some(filter) = chain {
c.as_str().to_ascii_lowercase() == filter.to_ascii_lowercase()
} else {
true
}
})
.for_each(|(_, e)| {
let ee = e.clone();
endpoint_vec.extend(ee.into_iter());
});
}
});
endpoint_vec
}
pub fn print_registries(&self) {
self.registries.iter().for_each(|(_name, reg)| {
println!("- [{}] {:?} {:?}", if reg.enabled { "X" } else { " " }, reg.name, reg.url);
})
}
pub fn print_summary(&self) {
self.registries.iter().for_each(|(_name, reg)| {
println!(
"- [{}] {} - {}",
if reg.enabled { "X" } else { " " },
reg.name,
if let Some(url) = ®.url { url } else { "n/a" }
);
println!(" rpc endpoints: {:?}", reg.rpc_endpoints.len());
println!(" last update: {:?}", reg.last_update);
})
}
}
impl Default for LocalData {
fn default() -> Self {
Self { file: Self::get_default_file(), registries: HashMap::new(), last_update: None }
}
}
#[cfg(test)]
mod test_local_data {
use super::*;
#[test]
fn test_builder() {
let data = LocalData::init(&LocalData::get_default_file(), true)
.expect("Forced init should work")
.save()
.expect("Saving data should work")
.load().expect("Load works")
.add_registry(Registry::new("SubRPC", "https://raw.githubusercontent.com/chevdor/subrpc/master/registry/subrpc.json"))
.add_registry(Registry::new("SubRPC Gist", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data.json"))
.refresh()
.save().expect("Saving works");
println!("{data:#?}");
}
#[test]
fn test_merge() {
let data = LocalData::init(&LocalData::get_default_file(), true)
.expect("Forced init should work")
.add_registry(Registry::new("SubRPC Gist 1", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data.json"))
.add_registry(Registry::new("SubRPC Gist 2", "https://gist.githubusercontent.com/chevdor/a8b381911c28f6de02dde62ed1a17dec/raw/6992b0a2924f80f691e4844c1731564f0e2a62ec/data2.json"))
.refresh()
.save().expect("Saving works");
assert_eq!(2, data.registries.len());
println!("{data:#?}");
}
}