use std::fmt::{self, Display, Formatter};
use std::sync::{LazyLock, Mutex};
use color_eyre::eyre::{eyre, Result};
use schnellru::{ByLength, LruMap};
use autonomi::client::files::archive_public::ArchiveAddress;
use crate::cache::directory_with_name::HISTORY_NAMES;
use crate::client::DwebClient;
use crate::files::directory::Tree;
use crate::helpers::convert::*;
use crate::history::HistoryAddress;
const WITH_PORT_CAPACITY: u32 = u16::MAX as u32;
const DETERMINISTIC_PORT_RANGE: u64 = 20_000;
const DETERMINISTIC_PORT_BASE: u64 = 30_000;
pub fn key_for_directory_versions_with_port(archive_address: ArchiveAddress) -> String {
format!("{}", archive_address.to_hex()).to_ascii_lowercase()
}
pub static DIRECTORY_VERSIONS_WITH_PORT: LazyLock<Mutex<LruMap<String, DirectoryVersionWithPort>>> =
LazyLock::new(|| {
Mutex::<LruMap<String, DirectoryVersionWithPort>>::new(LruMap::<
String,
DirectoryVersionWithPort,
>::new(ByLength::new(
WITH_PORT_CAPACITY,
)))
});
fn deterministic_port_from_archive_address(archive_address: ArchiveAddress) -> u16 {
let hex_string = archive_address.to_hex();
let hex_suffix = &hex_string[48..];
let number = u64::from_str_radix(hex_suffix, 16).unwrap_or(0);
((number % DETERMINISTIC_PORT_RANGE) + DETERMINISTIC_PORT_BASE) as u16
}
#[derive(Clone)]
pub struct DirectoryVersionWithPort {
pub port: u16,
pub history_address: Option<HistoryAddress>,
pub version: Option<u64>,
pub archive_address: ArchiveAddress,
pub directory_tree: Tree,
#[cfg(feature = "fixed-dweb-hosts")]
is_fixed_webname: bool,
}
impl Display for DirectoryVersionWithPort {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "DirectoryVersionWithPort\n port: {}\n history_address: {}\n version: {}\n archive_address: {}",
self.port,
if self.history_address.is_some() { self.history_address.unwrap().to_hex() } else { "None".to_string() },
if self.version.is_some() { self.version.unwrap() } else { 0 },
self.archive_address.to_string(),
)
}
}
impl DirectoryVersionWithPort {
pub fn new(
port: u16,
history_address: Option<HistoryAddress>,
version: Option<u64>,
archive_address: ArchiveAddress,
directory_tree: Tree,
) -> DirectoryVersionWithPort {
DirectoryVersionWithPort {
port,
history_address,
version,
archive_address,
directory_tree,
#[cfg(feature = "fixed-dweb-hosts")]
is_fixed_webname: false,
}
}
}
pub async fn lookup_or_create_directory_version_with_port(
client: &DwebClient,
address_or_name: &String,
version: Option<u64>,
) -> Result<(DirectoryVersionWithPort, bool)> {
let (history_address, archive_address) = tuple_from_address_or_name(address_or_name);
let mut history_address = history_address;
if history_address.is_none() && archive_address.is_none() {
if let Ok(lock) = &mut HISTORY_NAMES.lock() {
let cached_address = lock.get(address_or_name);
if cached_address.is_none() {
return Err(eyre!(
"Unrecognised DWEB-NAME or invalid address: '{address_or_name}'"
));
} else {
history_address = Some(*cached_address.unwrap());
}
};
};
let archive_address = if archive_address.is_none() {
let min_entry = version.unwrap_or(1);
match crate::history::History::<Tree>::from_history_address(
client.clone(),
history_address.unwrap(),
false,
min_entry,
)
.await
{
Ok(mut history) => {
let ignore_pointer = false;
let version = version.unwrap_or(history.num_versions().unwrap_or(0));
let archive_address = match history
.get_version_entry_value(version, ignore_pointer)
.await
{
Ok(archive_address) => archive_address,
Err(e) => {
let msg =
format!("Unable to get archive address for version {version} - {e}");
println!("DEBUG {msg}");
return Err(eyre!(msg));
}
};
archive_address
}
Err(e) => {
let msg = format!("Unable to create directory version because from_history_address() failed - {e}");
println!("DEBUG {msg}");
return Err(eyre!(msg));
}
}
} else {
archive_address.unwrap()
};
let key = key_for_directory_versions_with_port(archive_address);
if let Ok(lock) = &mut DIRECTORY_VERSIONS_WITH_PORT.lock() {
if let Some(directory_version) = lock.get(&key) {
return Ok((directory_version.clone(), true));
};
};
let directory_tree = match Tree::from_archive_address(client, archive_address).await {
Ok(directory_tree) => directory_tree,
Err(e) => {
let msg = format!("Failed to fetch archive from network - {e}");
println!("DEBUG {msg}");
return Err(eyre!(msg));
}
};
let port = deterministic_port_from_archive_address(archive_address);
let port = if port_check::is_local_ipv4_port_free(port) {
port
} else {
match port_check::free_local_port() {
Some(free_port) => {
println!(
"DEBUG Deterministic port {} is not available, using random port {} instead",
port, free_port
);
free_port
}
None => {
return Err(eyre!(
"Unable to spawn a dweb server - no free ports available"
));
}
}
};
let directory_version = DirectoryVersionWithPort::new(
port,
history_address,
version,
archive_address,
directory_tree,
);
if let Ok(lock) = &mut DIRECTORY_VERSIONS_WITH_PORT.lock() {
let key = key_for_directory_versions_with_port(archive_address);
if lock.insert(key, directory_version.clone()) {
return Ok((directory_version, false));
} else {
let msg = format!("Failed to add new DirectoryVersionWithPort to the cache");
println!("DEBUG {msg}");
return Err(eyre!(msg));
}
}
Ok((directory_version, false))
}