mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
global: snapshot
This commit is contained in:
@@ -9,9 +9,8 @@ use brk_rpc::{Auth, Client};
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::{default_brk_path, dot_brk_path, website::Website};
|
||||
use crate::{default_brk_path, dot_brk_path, fix_user_path, website::Website};
|
||||
|
||||
const DOWNLOADS: &str = "downloads";
|
||||
|
||||
#[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[command(version, about)]
|
||||
@@ -234,58 +233,36 @@ Finally, you can run the program with '-h' for help."
|
||||
self.bitcoindir
|
||||
.as_ref()
|
||||
.map_or_else(Client::default_bitcoin_path, |s| {
|
||||
Self::fix_user_path(s.as_ref())
|
||||
fix_user_path(s.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blocksdir(&self) -> PathBuf {
|
||||
self.blocksdir.as_ref().map_or_else(
|
||||
|| self.bitcoindir().join("blocks"),
|
||||
|blocksdir| Self::fix_user_path(blocksdir.as_str()),
|
||||
|blocksdir| fix_user_path(blocksdir.as_str()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn brkdir(&self) -> PathBuf {
|
||||
self.brkdir
|
||||
.as_ref()
|
||||
.map_or_else(default_brk_path, |s| Self::fix_user_path(s.as_ref()))
|
||||
.map_or_else(default_brk_path, |s| fix_user_path(s.as_ref()))
|
||||
}
|
||||
|
||||
pub fn harsdir(&self) -> PathBuf {
|
||||
self.brkdir().join("hars")
|
||||
}
|
||||
|
||||
pub fn downloads_dir(&self) -> PathBuf {
|
||||
dot_brk_path().join(DOWNLOADS)
|
||||
}
|
||||
|
||||
fn path_cookiefile(&self) -> PathBuf {
|
||||
self.rpccookiefile.as_ref().map_or_else(
|
||||
|| self.bitcoindir().join(".cookie"),
|
||||
|p| Self::fix_user_path(p.as_str()),
|
||||
|p| fix_user_path(p.as_str()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fix_user_path(path: &str) -> PathBuf {
|
||||
let fix = move |pattern: &str| {
|
||||
if path.starts_with(pattern) {
|
||||
let path = &path
|
||||
.replace(&format!("{pattern}/"), "")
|
||||
.replace(pattern, "");
|
||||
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
|
||||
Some(Path::new(&home).join(path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
fix("~").unwrap_or_else(|| fix("$HOME").unwrap_or_else(|| PathBuf::from(&path)))
|
||||
}
|
||||
|
||||
pub fn website(&self) -> Website {
|
||||
self.website.unwrap_or(Website::Bitview)
|
||||
self.website.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fetch(&self) -> bool {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
io::Cursor,
|
||||
path::PathBuf,
|
||||
thread::{self, sleep},
|
||||
time::Duration,
|
||||
@@ -16,8 +15,7 @@ use brk_iterator::Blocks;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_query::AsyncQuery;
|
||||
use brk_reader::Reader;
|
||||
use brk_server::{Server, VERSION};
|
||||
use importmap::ImportMap;
|
||||
use brk_server::{Server, WebsiteSource};
|
||||
use tracing::info;
|
||||
use vecdb::Exit;
|
||||
|
||||
@@ -25,7 +23,7 @@ mod config;
|
||||
mod paths;
|
||||
mod website;
|
||||
|
||||
use crate::{config::Config, paths::*};
|
||||
use crate::{config::Config, paths::*, website::Website};
|
||||
|
||||
pub fn main() -> color_eyre::Result<()> {
|
||||
// Can't increase main thread's stack size, thus we need to use another thread
|
||||
@@ -80,89 +78,22 @@ pub fn run() -> color_eyre::Result<()> {
|
||||
|
||||
let query = AsyncQuery::build(&reader, &indexer, &computer, Some(mempool));
|
||||
|
||||
let website = config.website();
|
||||
|
||||
let downloads_path = config.downloads_dir();
|
||||
let data_path = config.brkdir();
|
||||
|
||||
let future = async move {
|
||||
// Try to find local dev directories - check cwd and parent directories
|
||||
let find_dev_dirs = || -> Option<(PathBuf, PathBuf)> {
|
||||
let mut dir = std::env::current_dir().ok()?;
|
||||
loop {
|
||||
let websites = dir.join("websites");
|
||||
let modules = dir.join("modules");
|
||||
if websites.exists() && modules.exists() {
|
||||
return Some((websites, modules));
|
||||
}
|
||||
// Stop at workspace root (crates/ indicates we're there)
|
||||
if dir.join("crates").exists() {
|
||||
return None;
|
||||
}
|
||||
dir = dir.parent()?.to_path_buf();
|
||||
}
|
||||
};
|
||||
|
||||
let dev_dirs = find_dev_dirs();
|
||||
let is_dev = dev_dirs.is_some();
|
||||
|
||||
let bundle_path = if website.is_some() {
|
||||
let websites_path = if let Some((websites, _modules)) = dev_dirs {
|
||||
websites
|
||||
} else {
|
||||
let downloaded_brk_path = downloads_path.join(format!("brk-{VERSION}"));
|
||||
let downloaded_websites_path = downloaded_brk_path.join("websites");
|
||||
|
||||
if !fs::exists(&downloaded_websites_path)? {
|
||||
info!("Downloading source from Github...");
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/bitcoinresearchkit/brk/archive/refs/tags/v{VERSION}.zip",
|
||||
);
|
||||
|
||||
let response = minreq::get(url).with_timeout(60).send()?;
|
||||
let bytes = response.as_bytes();
|
||||
let cursor = Cursor::new(bytes);
|
||||
|
||||
let mut zip = zip::ZipArchive::new(cursor).unwrap();
|
||||
|
||||
zip.extract(downloads_path).unwrap();
|
||||
}
|
||||
|
||||
downloaded_websites_path
|
||||
};
|
||||
|
||||
Some(websites_path.join(website.to_folder_name()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Generate import map for cache busting (disabled in dev mode)
|
||||
if let Some(ref path) = bundle_path {
|
||||
let map = if is_dev {
|
||||
ImportMap::empty()
|
||||
} else {
|
||||
match ImportMap::scan(path, "") {
|
||||
Ok(map) => map,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate importmap: {e}");
|
||||
ImportMap::empty()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let html_path = path.join("index.html");
|
||||
if let Ok(html) = fs::read_to_string(&html_path)
|
||||
&& let Some(updated) = map.update_html(&html)
|
||||
{
|
||||
let _ = fs::write(&html_path, updated);
|
||||
if !is_dev {
|
||||
info!("Updated importmap in index.html");
|
||||
}
|
||||
let website_source = match config.website() {
|
||||
Website::Enabled(false) => WebsiteSource::Disabled,
|
||||
Website::Path(p) => WebsiteSource::Filesystem(p),
|
||||
Website::Enabled(true) => {
|
||||
// Prefer local filesystem if available, otherwise use embedded
|
||||
match find_local_website_dir() {
|
||||
Some(path) => WebsiteSource::Filesystem(path),
|
||||
None => WebsiteSource::Embedded,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let server = Server::new(&query, data_path, bundle_path);
|
||||
let future = async move {
|
||||
let server = Server::new(&query, data_path, website_source);
|
||||
|
||||
tokio::spawn(async move {
|
||||
server.serve(true).await.unwrap();
|
||||
@@ -201,3 +132,12 @@ pub fn run() -> color_eyre::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Path to website directory relative to this crate (only valid at dev machine)
|
||||
const DEV_WEBSITE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../website");
|
||||
|
||||
/// Returns local website path if it exists (dev mode)
|
||||
fn find_local_website_dir() -> Option<PathBuf> {
|
||||
let path = PathBuf::from(DEV_WEBSITE_DIR);
|
||||
path.exists().then_some(path)
|
||||
}
|
||||
|
||||
@@ -12,3 +12,12 @@ pub fn dot_brk_log_path() -> PathBuf {
|
||||
pub fn default_brk_path() -> PathBuf {
|
||||
dot_brk_path()
|
||||
}
|
||||
|
||||
pub fn fix_user_path(path: &str) -> PathBuf {
|
||||
if let Some(rest) = path.strip_prefix("~/").or(path.strip_prefix("$HOME/"))
|
||||
&& let Ok(home) = std::env::var("HOME")
|
||||
{
|
||||
return PathBuf::from(home).join(rest);
|
||||
}
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
use clap::ValueEnum;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, ValueEnum)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
use crate::paths::fix_user_path;
|
||||
|
||||
/// Website configuration:
|
||||
/// - `true` or omitted: serve embedded website
|
||||
/// - `false`: disable website serving
|
||||
/// - `"/path/to/website"`: serve custom website from path
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Website {
|
||||
None,
|
||||
Bitview,
|
||||
Custom,
|
||||
Enabled(bool),
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl Website {
|
||||
pub fn is_none(&self) -> bool {
|
||||
self == &Self::None
|
||||
}
|
||||
|
||||
pub fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
pub fn to_folder_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Custom => "custom",
|
||||
Self::Bitview => "bitview",
|
||||
Self::None => unreachable!(),
|
||||
}
|
||||
impl Default for Website {
|
||||
fn default() -> Self {
|
||||
Self::Enabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl FromStr for Website {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s.to_lowercase().as_str() {
|
||||
"true" | "1" | "yes" | "on" => Self::Enabled(true),
|
||||
"false" | "0" | "no" | "off" => Self::Enabled(false),
|
||||
_ => Self::Path(fix_user_path(s)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user