use crate::{ bcmr::{parsedbcmr::parse_all_bcmr_identities, utilurl::get_url}, db::{ bcmr::{delete_entries_for_well_known, insert_well_known_bcmr}, DBPool, }, }; use anyhow::Result; use log::{info, warn}; use serde_json::Value; use std::{ sync::{atomic::AtomicBool, Arc}, thread::{self, JoinHandle}, time::{Duration, Instant}, }; // List of 'trust me bro' sources of truth const WELL_KNOWN: [(&str, &str); 1] = [( "otr", "https://otr.cash/.well-known/bitcoin-cash-metadata-registry.json", )]; pub struct WellKnownDownloader { db: DBPool, keep_running: Arc, download_thread: Option>, } const LOOP_SLEEP_TIME: Duration = Duration::from_secs(2); const UPDATE_INTERVAL: Duration = Duration::from_secs(3600 * 24); // once per day const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(120); const MAX_BCMR_SIZE: usize = 1024 * 1024 * 500; // 500 MB impl WellKnownDownloader { pub fn new(db: DBPool) -> Self { Self { db, keep_running: Arc::new(AtomicBool::new(true)), download_thread: None, } } pub fn start(&mut self) -> Result<()> { let db_cpy = self.db.clone(); let keep_running_cpy = self.keep_running.clone(); self.download_thread = Some( thread::Builder::new() .name("wellknown downloader".to_string()) .spawn(move || { let mut last_update: Option = None; loop { thread::sleep(LOOP_SLEEP_TIME); if !keep_running_cpy.load(std::sync::atomic::Ordering::Relaxed) { info!("Exiting bcmr well known thread"); return; } if let Some(last_time) = last_update { if Instant::now().duration_since(last_time) < UPDATE_INTERVAL { continue; } } last_update = Some(Instant::now()); for (source, url) in WELL_KNOWN { info!("bcmr: Fetching well known '{}' token list", source); let json_str: String = match get_url(url, DOWNLOAD_TIMEOUT, MAX_BCMR_SIZE) { Ok((json_str, _)) => json_str, Err(e) => { info!("bcmr: Failed to download well known url {}: {}", url, e); continue; } }; let json: Value = match serde_json::from_str(&json_str) { Ok(j) => j, Err(e) => { info!("bcmr: Failed to parse well known url {}: {}", url, e); continue; } }; let bcmr_entries = match parse_all_bcmr_identities(&json, true, source) { Ok(entries) => entries, Err(e) => { info!("bcmr: Failed to parse bcmr from well known url {}: {}", url, e); continue; } }; let mut tx = match db_cpy.get() { Ok(conn) => conn, Err(e) => { warn!("bcmr: Failed to get DB connection: {}", e); continue; } }; let tx = match tx.transaction() { Ok(tx) => tx, Err(e) => { warn!("bcmr: Failed to initiate DB transaction: {}", e); continue; } }; if let Err(e) = delete_entries_for_well_known(&tx, source) { warn!("bcmr: Failed to delete entries for well known source {}: {}", source, e); } for bcmr in bcmr_entries { if let Err(e) = insert_well_known_bcmr(&tx, source, &bcmr) { warn!("bcmr: Failed to insert an entry from well known source {}: {}", source, e); } } if let Err(e) = tx.commit() { warn!("bcmr: Failed to update DB entries for {}: {}", source, e); } } }}) .expect("failed to start bcmr wellknown thread"), ); Ok(()) } } impl Drop for WellKnownDownloader { fn drop(&mut self) { self.keep_running .store(false, std::sync::atomic::Ordering::SeqCst); if let Some(thread) = self.download_thread.take() { // Wake the thread in case it is sleeping thread.thread().unpark(); match thread.join() { Ok(_) => info!("bcmr well knownthread done"), Err(e) => warn!("Failed to join bcmr wellknown thread: {:?}", e), } } } }