Add logging, fix devenv up

This commit is contained in:
Alexander
2026-06-30 12:17:48 +02:00
parent ebf1c5b5e1
commit d1e1ac97c4
32 changed files with 1549 additions and 129 deletions
+142
View File
@@ -0,0 +1,142 @@
use std::{
collections::BTreeMap,
path::PathBuf,
sync::{Arc, RwLock},
thread,
time::Duration,
};
use fuser::INodeNo;
use sea_orm::{DatabaseConnection, EntityTrait};
use tokio_stream::StreamExt;
use crate::item::Item;
use crate::origins::FileWatcher;
use crate::origins::network::transport::NetworkTransport;
use crate::proto::ManifestEntry as ProtoManifestEntry;
use tracing::{info, warn};
const POLL_FALLBACK_INTERVAL: Duration = Duration::from_secs(30);
pub struct NetworkOriginFileWatcher {
transport: NetworkTransport,
runtime_handle: tokio::runtime::Handle,
client: DatabaseConnection,
destination: PathBuf,
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
}
impl NetworkOriginFileWatcher {
#[allow(clippy::too_many_arguments)]
pub fn new(
transport: NetworkTransport,
runtime_handle: tokio::runtime::Handle,
client: DatabaseConnection,
destination: PathBuf,
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
) -> Self {
return NetworkOriginFileWatcher {
transport,
runtime_handle,
client,
destination,
latest_manifest,
};
}
}
/// `files` is currently unused at runtime by the network watcher — the
/// authoritative post-reconcile snapshot is written to the DB, and the next
/// FUSE `readdir`/`lookup` will reflect it because FuseFs re-locks the map.
/// The trait signature still requires it for parity with LocalOriginFileWatcher;
/// a future refactor can rebuild the map in place here.
impl FileWatcher for NetworkOriginFileWatcher {
fn watch(&self, files: Arc<std::sync::Mutex<BTreeMap<INodeNo, Item>>>) {
let runtime_handle = self.runtime_handle.clone();
let state = WatcherState {
transport: self.transport.clone(),
client: self.client.clone(),
destination: self.destination.clone(),
latest_manifest: self.latest_manifest.clone(),
files,
};
thread::spawn(move || {
runtime_handle.block_on(state.run_loop());
});
}
}
#[derive(Clone)]
struct WatcherState {
transport: NetworkTransport,
client: DatabaseConnection,
destination: PathBuf,
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
files: Arc<std::sync::Mutex<BTreeMap<INodeNo, Item>>>,
}
impl WatcherState {
async fn run_loop(self) {
loop {
match self.transport.subscribe_events().await {
Ok(mut stream) => {
info!("network watcher: subscribed to /events");
while let Some(item) = stream.next().await {
match item {
Ok(_event) => {
if let Err(e) = self.reconcile_once().await {
warn!(error = %e, "network watcher: reconcile after event failed");
}
}
Err(e) => {
warn!(error = %e, "network watcher: stream error; reconnecting");
break;
}
}
}
}
Err(e) => {
warn!(error = %e, "network watcher: subscribe failed; will retry");
}
}
tokio::time::sleep(POLL_FALLBACK_INTERVAL).await;
if let Err(e) = self.reconcile_once().await {
warn!(error = %e, "network watcher: poll reconcile failed");
}
}
}
async fn reconcile_once(&self) -> anyhow::Result<()> {
let client_entries: Vec<(u64, u64)> = crate::db::entities::Entity::find()
.all(&self.client)
.await?
.into_iter()
.map(|m| (m.inode as u64, m.hash as u64))
.collect();
let response = self.transport.reconcile(client_entries).await?;
let mut changed_or_deleted: Vec<i64> =
response.changed.iter().map(|ih| ih.inode as i64).collect();
changed_or_deleted.extend(response.deleted.iter().map(|i| *i as i64));
crate::db::cache::delete_cached_bytes_for(&changed_or_deleted, &self.client).await;
let wanted: Vec<u64> = response.changed.iter().map(|ih| ih.inode).collect();
let mut current_manifest = self.latest_manifest.read().unwrap().clone();
if !wanted.is_empty() {
let entries = self.transport.get_metadata(wanted).await?;
for entry in entries {
current_manifest.insert(entry.id, entry);
}
}
for inode in &response.deleted {
current_manifest.remove(inode);
}
*self.latest_manifest.write().unwrap() = current_manifest.clone();
let _ = self.destination.clone();
let _ = self.files.clone();
return Ok(());
}
}