diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 0ef81bb..7966a55 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -34,6 +34,8 @@ use diag::{ use log::{error, info}; use qmdl_store::RecordingStoreError; use rayhunter::diag_device::DiagDevice; +use std::path::Path; +use tokio::fs; use tokio::net::TcpListener; use tokio::select; use tokio::sync::mpsc::{self, Sender}; @@ -109,7 +111,27 @@ async fn init_qmdl_store(config: &config::Config) -> Result { error!("failed to parse QMDL manifest: {err}"); info!("creating new empty manifest..."); - Ok(RecordingStore::create(&config.qmdl_store_path).await?) + let mut recording_store = RecordingStore::create(&config.qmdl_store_path).await?; + info!("parsing existing qmdl files into recording store..."); + let path = Path::new(&config.qmdl_store_path); + let mut entries = fs::read_dir(path).await?; + + // We might want to sort these newest to oldest so we don't have entries in manifest.toml in random order + while let Some(entry) = entries.next_entry().await? { + let file_name = entry.file_name(); + let file_name_str = match file_name.to_str() { + Some(s) => s, + None => continue, // skip non-UTF-8 names + }; + + if file_name_str.ends_with(".qmdl") { + let name = file_name_str.trim_end_matches(".qmdl"); + info!("making entry for {}", name); + recording_store.new_entry_from_existing(name.to_string()).await?; + } + } + + Ok(recording_store) } Err(err) => Err(err.into()), } diff --git a/daemon/src/qmdl_store.rs b/daemon/src/qmdl_store.rs index 389ebf8..2261b0f 100644 --- a/daemon/src/qmdl_store.rs +++ b/daemon/src/qmdl_store.rs @@ -1,7 +1,9 @@ use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; +use std::time::UNIX_EPOCH; -use chrono::{DateTime, Local}; +use chrono::{DateTime, Local, TimeZone}; +use rayhunter::analysis; use rayhunter::util::RuntimeMetadata; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -71,6 +73,7 @@ impl ManifestEntry { } } + pub fn get_qmdl_filepath>(&self, path: P) -> PathBuf { let mut filepath = path.as_ref().join(&self.name); filepath.set_extension("qmdl"); @@ -171,6 +174,40 @@ impl RecordingStore { self.write_manifest().await?; Ok((qmdl_file, analysis_file)) } + + pub async fn new_entry_from_existing(&mut self, name: String) -> Result<(File, File), RecordingStoreError> { + // if we've already got an entry open, close it + if self.current_entry.is_some() { + self.close_current_entry().await?; + } + let mut new_entry = ManifestEntry::new(); + new_entry.name = name; + let qmdl_filepath = new_entry.get_qmdl_filepath(&self.path); + let qmdl_file = File::open(&qmdl_filepath) + .await + .map_err(RecordingStoreError::ReadFileError)?; + let qmdl_meta = qmdl_file.metadata().await.map_err(RecordingStoreError::ReadFileError)?; + let analysis_filepath = new_entry.get_analysis_filepath(&self.path); + let analysis_file = File::open(&analysis_filepath) + .await + .map_err(RecordingStoreError::ReadFileError)?; + + let timestamp = Local.timestamp_opt(new_entry.name.parse::().expect("Invalid timestamp"), 0).unwrap(); + new_entry.start_time = timestamp; + + // I can't think of a better way to find this + let update = qmdl_meta.modified().expect("no mod date").duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + new_entry.last_message_time = Some(Local.timestamp_opt(update.as_secs().try_into().expect("error"), 0).unwrap()); + + new_entry.analysis_size_bytes = analysis_file.metadata().await.map_err(RecordingStoreError::ReadFileError)?.len().try_into().expect("file too large"); + new_entry.qmdl_size_bytes = qmdl_meta.len().try_into().expect("file too large"); + + self.manifest.entries.push(new_entry); + self.current_entry = Some(self.manifest.entries.len() - 1); + self.write_manifest().await?; + Ok((qmdl_file, analysis_file)) + } // Returns the corresponding QMDL file for a given entry pub async fn open_entry_qmdl(&self, entry_index: usize) -> Result {