Skip to content

Commit

Permalink
Merge pull request #8 from ALPHA-g-Experiment/external
Browse files Browse the repository at this point in the history
Log external resources
  • Loading branch information
DJDuque authored Oct 10, 2024
2 parents 8301f23 + e6f223c commit e814488
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 14 deletions.
65 changes: 65 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dialoguer = "0.11.0"
directories = "5.0.1"
indent = "0.1.1"
indicatif = "0.17.8"
jiff = "0.1.13"
regex = "1.11.0"
reqwest = { version = "0.12.7", features = ["blocking"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
Expand Down
10 changes: 5 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ pub struct ChronoboxTableConfig {
}

#[derive(Clone, Debug, Deserialize)]
struct ExternalResourceConfig {
base_path: PathBuf,
header: Option<String>,
pub struct ExternalResourceConfig {
pub base_path: PathBuf,
pub header: Option<String>,
#[serde(default)]
include_description: bool,
pub include_description: bool,
#[serde(default)]
include_attachment: bool,
pub include_attachment: bool,
}
53 changes: 46 additions & 7 deletions src/elog.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::config::{DataHandlerConfig, EntryConfig, LogRule};
use crate::data_handler::{get_chronobox_plot, ChronoboxTimestampsArgs, Record, SpillLog};
use anyhow::{ensure, Context, Result};
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;

#[derive(Debug)]
Expand Down Expand Up @@ -86,12 +87,9 @@ impl ElogEntry {
loggable: &LoggableRecord,
odb: &serde_json::Value,
handler_config: &DataHandlerConfig,
external_resources: &mut HashMap<PathBuf, VecDeque<PathBuf>>,
) {
let mut new_text = format!(
"{} - {}\n",
loggable.record.sequencer_name.to_uppercase(),
loggable.record.event_description
);
let mut sections = Vec::new();

if let Some(table_config) = &loggable.config.chronobox_table {
let mut header = table_config.channel_names.clone();
Expand Down Expand Up @@ -132,9 +130,50 @@ impl ElogEntry {
let mut builder = tabled::builder::Builder::new();
builder.push_record(header);
builder.push_record(data);
new_text.push_str(&format!("{}\n", builder.build()));
sections.push(builder.build().to_string());
}

self.text.push_str(&indent::indent_by(4, new_text));
for config in &loggable.config.external_resources {
let attachment = external_resources
.get_mut(&config.base_path)
.and_then(|paths| paths.pop_front());

let mut text = String::new();

if let Some(header) = &config.header {
text.push_str(&format!("{}", header));
}
if config.include_description {
let description = attachment
.clone()
.and_then(|path| std::fs::read_to_string(path.with_extension("txt")).ok())
.unwrap_or_else(|| String::from("<MISSING_DESCRIPTION> "));

text.push_str(&description);
}
if config.include_attachment {
if let Some(path) = attachment {
self.attachments.push(path);
text.push_str(&format!("elog:/{}", self.attachments.len()));
} else {
text.push_str("<MISSING_ATTACHMENT>");
}
}

if !text.is_empty() {
sections.push(text);
}
}

if !sections.is_empty() {
let text = format!(
"{} - {}\n{}\n\n",
loggable.record.sequencer_name.to_uppercase(),
loggable.record.event_description,
sections.join("\n\n")
);

self.text.push_str(&indent::indent_by(4, text));
}
}
}
102 changes: 102 additions & 0 deletions src/external_resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use anyhow::{Context, Result};
use jiff::{tz::TimeZone, Timestamp, Zoned};
use serde_json::Value;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::LazyLock;

// Return the (start_time, stop_time) of a run given the final JSON ODB.
pub fn run_time_limits(odb: &Value) -> Result<(Zoned, Zoned)> {
let tz = TimeZone::get("Europe/Zurich").context("failed to get `Europe/Zurich` timezone")?;

let start_time = odb
.pointer("/Runinfo/Start time binary")
.and_then(Value::as_str)
.and_then(|s| s.strip_prefix("0x"))
.context("failed to get binary start time")
.and_then(|s| i64::from_str_radix(s, 16).map_err(|e| anyhow::anyhow!(e)))
.context("failed to parse start time as i64")?;
let start_time =
Timestamp::from_second(start_time).context("failed to create start timestamp")?;
let start_time = Zoned::new(start_time, tz.clone());

let stop_time = odb
.pointer("/Runinfo/Stop time binary")
.and_then(Value::as_str)
.and_then(|s| s.strip_prefix("0x"))
.context("failed to get binary stop time")
.and_then(|s| i64::from_str_radix(s, 16).map_err(|e| anyhow::anyhow!(e)))
.context("failed to parse stop time as i64")?;
let stop_time = Timestamp::from_second(stop_time).context("failed to create stop timestamp")?;
let stop_time = Zoned::new(stop_time, tz);

Ok((start_time, stop_time))
}

static FILE_PATTERN: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new(r"^\d{2}\d{2}_\d{2}\.\d{3}\.png$").unwrap());
// An external resource is created with the name
// `base_path/<year>/<month>/<day>/hhmm_ss.mss.png`
// Return all paths in chronological order.
pub fn find_external_resources(
base_path: PathBuf,
start_time: Zoned,
stop_time: Zoned,
) -> VecDeque<PathBuf> {
let first_possible_entry = base_path
.join(start_time.year().to_string())
.join(format!("{:02}", start_time.month()))
.join(format!("{:02}", start_time.day()))
.join(format!(
"{:02}{:02}_{:02}.000.png",
start_time.hour(),
start_time.minute(),
start_time.second(),
));
let last_possible_entry = base_path
.join(stop_time.year().to_string())
.join(format!("{:02}", stop_time.month()))
.join(format!("{:02}", stop_time.day()))
.join(format!(
"{:02}{:02}_{:02}.999.png",
stop_time.hour(),
stop_time.minute(),
stop_time.second(),
));

let mut paths = VecDeque::new();

let mut date = start_time.date();
while date <= stop_time.date() {
let folder = base_path
.join(date.year().to_string())
.join(format!("{:02}", date.month()))
.join(format!("{:02}", date.day()));
if let Ok(entries) = std::fs::read_dir(&folder) {
let mut entries = entries
.filter_map(|res| res.map(|e| e.path()).ok())
.filter(|p| {
p.is_file()
&& p.file_name()
.and_then(|f| f.to_str())
.map(|f| FILE_PATTERN.is_match(f))
.unwrap_or(false)
})
.collect::<Vec<_>>();

if date == start_time.date() {
entries.retain(|p| p > &first_possible_entry);
}
if date == stop_time.date() {
entries.retain(|p| p < &last_possible_entry);
}
entries.sort_unstable();

paths.extend(entries);
}

date = date.tomorrow().unwrap();
}

paths
}
45 changes: 43 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::config::{Config, Logbook};
use crate::data_handler::{get_final_odb, get_sequencer_headers, get_spill_log};
use crate::external_resources::{find_external_resources, run_time_limits};
use crate::summary::spill_log_summary;
use anyhow::{ensure, Context, Result};
use clap::Parser;
use dialoguer::{theme::ColorfulTheme, Input, Select};
use elog::{loggable_records, ElogEntry};
use indicatif::{ProgressBar, ProgressStyle};
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::io::Write;
use std::path::PathBuf;
Expand All @@ -15,6 +17,7 @@ use tempfile::NamedTempFile;
mod config;
mod data_handler;
mod elog;
mod external_resources;
mod summary;

#[derive(Parser)]
Expand Down Expand Up @@ -110,14 +113,46 @@ fn main() -> Result<()> {

records
};
let (start_time, stop_time) =
run_time_limits(&final_odb).context("failed to get run time limits from the final ODB")?;
let base_paths = records
.iter()
.flat_map(|loggable| loggable.config.external_resources.clone())
.map(|config| config.base_path)
.collect::<HashSet<_>>();
let mut external_resources = base_paths
.into_iter()
.map(|base_path| {
(
base_path.clone(),
find_external_resources(base_path, start_time.clone(), stop_time.clone()),
)
})
.collect::<HashMap<_, _>>();

let mut elog_entry = ElogEntry::new();
elog_entry.text.push_str(&format!(
"Run started: {} at {}\n",
start_time.date(),
start_time.time()
));
elog_entry.text.push_str(&format!(
"Run stopped: {} at {}\n",
stop_time.date(),
stop_time.time()
));
elog_entry.text.push_str("\n");

spinner.set_message("Logging header...");
/*
if let Ok(path) = get_sequencer_headers(args.run_number, &config.data_handler) {
elog_entry.attachments.push(path);
elog_entry.text.push_str("Sequencer: elog:/1\n");
elog_entry.text.push_str(&format!(
"Sequencer: elog:/{}\n",
elog_entry.attachments.len()
));
}
*/
if let Ok(path) = spill_log_summary(&spill_log, &config.spill_log_columns) {
elog_entry.attachments.push(path);
elog_entry.text.push_str(&format!(
Expand All @@ -129,7 +164,13 @@ fn main() -> Result<()> {

spinner.set_message("Logging records...");
for loggable in records {
elog_entry.add_record(args.run_number, &loggable, &final_odb, &config.data_handler);
elog_entry.add_record(
args.run_number,
&loggable,
&final_odb,
&config.data_handler,
&mut external_resources,
);
}

let mut temp_text =
Expand Down

0 comments on commit e814488

Please sign in to comment.