use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use crate::adapters::{self, Storage};
use crate::error::{ApiError, ApiResult};
use crate::registry::Registry;
use crate::settings::SettingsStore;
use crate::vault::PathCache;
#[derive(Debug, Clone, Default)]
pub struct HttpClientOptions {
pub proxy: Option<String>,
pub ca_cert: Option<PathBuf>,
pub insecure_tls: bool,
}
fn build_http_client(options: &HttpClientOptions) -> anyhow::Result<reqwest::Client> {
let mut builder =
reqwest::Client::builder().connect_timeout(std::time::Duration::from_secs(10));
if let Some(proxy) = options.proxy.as_deref() {
builder = builder.proxy(reqwest::Proxy::all(proxy)?);
}
if let Some(path) = options.ca_cert.as_deref() {
let bytes = std::fs::read(path).map_err(|error| {
anyhow::anyhow!("读取 HTTP CA 证书 {} 失败: {error}", path.display())
})?;
let certificate = reqwest::Certificate::from_pem(&bytes)
.or_else(|_| reqwest::Certificate::from_der(&bytes))
.map_err(|error| {
anyhow::anyhow!("解析 HTTP CA 证书 {} 失败: {error}", path.display())
})?;
builder = builder.add_root_certificate(certificate);
}
if options.insecure_tls {
builder = builder.danger_accept_invalid_certs(true);
}
Ok(builder.build()?)
}
#[derive(Clone)]
pub struct AppState(Arc<Inner>);
pub struct Inner {
pub registry: Registry,
pub settings: SettingsStore,
pub cache: PathCache,
pub content_cache: Arc<crate::cache::CacheStore>,
layout_cache: Mutex<HashMap<String, (Arc<crate::engine::FileLayout>, Instant)>>,
layout_probe_locks: Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
pub transfers: Arc<crate::transfer::TransferTracker>,
pub sessions: RwLock<HashSet<String>>,
pub uploading: Mutex<HashSet<String>>,
pub upload_progress: Mutex<HashMap<String, Arc<crate::engine::UploadProgress>>>,
pub mkdir_locks: Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
pub admin_password: Option<String>,
pub http: reqwest::Client,
}
impl std::ops::Deref for AppState {
type Target = Inner;
fn deref(&self) -> &Inner {
&self.0
}
}
impl AppState {
#[cfg(test)]
pub fn new(data_dir: PathBuf, admin_password: Option<String>) -> anyhow::Result<Self> {
Self::new_with_http_options(data_dir, admin_password, HttpClientOptions::default())
}
pub fn new_with_http_options(
data_dir: PathBuf,
admin_password: Option<String>,
http_options: HttpClientOptions,
) -> anyhow::Result<Self> {
let registry = Registry::load(data_dir.join("datasources.json"))?;
let settings = SettingsStore::load(data_dir.join("settings.json"))?;
let content_cache = Arc::new(crate::cache::CacheStore::new(data_dir.join("cache"))?);
let http = build_http_client(&http_options)?;
Ok(Self(Arc::new(Inner {
registry,
settings,
cache: PathCache::default(),
content_cache,
layout_cache: Mutex::new(HashMap::new()),
layout_probe_locks: Mutex::new(HashMap::new()),
transfers: Arc::new(crate::transfer::TransferTracker::default()),
sessions: RwLock::new(HashSet::new()),
uploading: Mutex::new(HashSet::new()),
upload_progress: Mutex::new(HashMap::new()),
mkdir_locks: Mutex::new(HashMap::new()),
admin_password,
http,
})))
}
pub fn adapter(&self, ds_id: &str) -> ApiResult<Box<dyn Storage>> {
let ds = self
.registry
.get(ds_id)
.ok_or_else(|| ApiError::NotFound(format!("数据源不存在: {ds_id}")))?;
adapters::make_with_token_persister(&ds, self.http.clone(), self.baidu_token_persister(&ds))
}
pub fn adapter_arc(&self, ds_id: &str) -> ApiResult<Arc<dyn Storage>> {
let ds = self
.registry
.get(ds_id)
.ok_or_else(|| ApiError::NotFound(format!("数据源不存在: {ds_id}")))?;
adapters::make_arc_with_token_persister(
&ds,
self.http.clone(),
self.baidu_token_persister(&ds),
)
}
fn baidu_token_persister(
&self,
datasource: &crate::registry::DataSource,
) -> Option<crate::adapters::baidupan::TokenPersister> {
if datasource.ds_type != "baidupan" {
return None;
}
let state = self.clone();
let id = datasource.id.clone();
Some(Arc::new(
move |access_token, refresh_token, access_expires_at| {
state.registry.update_baidu_tokens(
&id,
access_token,
refresh_token,
access_expires_at,
)
},
))
}
pub fn mkdir_lock(&self, ds: &str) -> Arc<tokio::sync::Mutex<()>> {
let mut locks = self.mkdir_locks.lock().unwrap();
Arc::clone(locks.entry(ds.to_string()).or_default())
}
pub fn cached_layout(&self, key: &str) -> Option<Arc<crate::engine::FileLayout>> {
const LAYOUT_CACHE_TTL: Duration = Duration::from_secs(300);
let mut cache = self.layout_cache.lock().unwrap();
match cache.get(key) {
Some((layout, saved_at)) if saved_at.elapsed() < LAYOUT_CACHE_TTL => {
Some(Arc::clone(layout))
}
Some(_) => {
cache.remove(key);
None
}
None => None,
}
}
pub fn put_cached_layout(&self, key: String, layout: Arc<crate::engine::FileLayout>) {
self.layout_cache
.lock()
.unwrap()
.insert(key, (layout, Instant::now()));
}
pub fn layout_probe_lock(&self, key: &str) -> Arc<tokio::sync::Mutex<()>> {
let mut locks = self.layout_probe_locks.lock().unwrap();
Arc::clone(locks.entry(key.to_string()).or_default())
}
pub fn root_key_of(&self, ds_id: &str) -> ApiResult<[u8; crate::crypto::SECRET_LEN]> {
let ds = self.datasource(ds_id)?;
if !ds.encryption_enabled {
return Err(ApiError::BadRequest("该数据源未启用加密".into()));
}
Ok(crate::crypto::derive_root_key(ds.password.as_bytes()))
}
pub fn root_key_candidates_of(
&self,
ds_id: &str,
) -> ApiResult<Vec<[u8; crate::crypto::SECRET_LEN]>> {
let ds = self.datasource(ds_id)?;
if !ds.encryption_enabled {
return Err(ApiError::BadRequest("该数据源未启用加密".into()));
}
let mut keys = vec![crate::crypto::derive_root_key(ds.password.as_bytes())];
if let Some(prev) = ds.prev_password {
keys.push(crate::crypto::derive_root_key(prev.as_bytes()));
}
Ok(keys)
}
pub fn datasource(&self, ds_id: &str) -> ApiResult<crate::registry::DataSource> {
self.registry
.get(ds_id)
.ok_or_else(|| ApiError::NotFound(format!("数据源不存在: {ds_id}")))
}
}