usenetnews-dynexp2 0.1.2

USENET news server expiry time dynamic tuning (for INN2)
Documentation
// Copyright 2022 Ian Jackson
// SPDX-License-Identifier: GPL-3.0-or-later
// There is NO WARRANTY.

use crate::prelude::*;

use std::time::SystemTime;

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct LogEntry {
  pub(crate) free_space: Space,
  pub(crate) action: LogAction,
  pub(crate) pattern: String,
  pub(crate) params: Parameters,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct LogAction {
  pub situation: Situation,
  pub action: &'static str,
  pub was: Days,
  pub now: Days,
  pub now_lambda: Lambda,
}

#[derive(Clone)]
pub struct LogCollection {
  when: String,
  pub(crate) by_history_file: BTreeMap<String, Vec<LogEntry>>,
}

#[derive(Debug)]
struct LogEntryInCollection<'e> {
  when: &'e str,
  entry: &'e LogEntry,
}

impl Default for LogCollection { fn default() -> Self { Self::new() } }

impl LogCollection {
  pub fn new() -> Self {
    Self::with_mock_time(SystemTime::now())
  }

  pub fn with_mock_time(when: SystemTime) -> Self {
    let when =
      humantime::format_rfc3339_seconds(when)
      .to_string()
      .replacen('T', " ", 1);

    LogCollection {
      when,
      by_history_file: BTreeMap::default(),
    }
  }
}

impl LogCollection {
  pub(crate) fn log(&mut self, pattern: &str, free_space: Space,
            params: &Parameters, action: LogAction) {
    self.by_history_file
      .entry(params.history.clone())
      .or_default()
      .push(LogEntry {
        pattern: pattern.into(),
        free_space,
        action,
        params: params.clone(),
      });
  }

  pub fn into_by_history_file_map(self) -> BTreeMap<String, Vec<LogEntry>> {
    self.by_history_file
  }
}

impl Display for LogEntryInCollection<'_> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let LogEntryInCollection {
      when,
      entry:
    LogEntry {
      free_space,
      pattern,
      params,
      action:
    LogAction {
      situation,
      action,
      was,
      now,
      now_lambda,
    }}} = self;

    write!(
      f, "{} {:12} {:#} [{:#}..{:#}] {:#} ->{:#} {} {:10} {}",
      when,
      free_space,
      situation,
      params.mindays,
      params.maxdays,
      was,
      now,
      now_lambda,
      action,
      pattern,
    )
  }
}

impl LogCollection {
  pub fn write_out(self) -> AR<()> {
    let when = &self.when;
    for (histfile, entries) in &self.by_history_file {
      (||{
        let f = File::options().write(true).append(true).create(true)
          .open(histfile)
          .context("open")?;
        let mut f = BufWriter::new(f);
        for entry in entries {
          writeln!(f, "{}", LogEntryInCollection { when, entry })
            .context("write")?;
          f.flush().context("flush")?;
        }
        AOk(())
      })()
        .with_context(|| histfile.to_string())
        .context("log file (histfile)")?;
    }
    Ok(())
  }
}

impl Debug for LogCollection {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let when = &self.when;
    for (histfile, entries) in &self.by_history_file {
      writeln!(f, "{:?}", histfile)?;
      //for entry in entries {
      //  writeln!(f, "  {:?}", entry)?;
      //}
      for entry in entries {
        writeln!(f, "  {}", LogEntryInCollection { when, entry })?;
      }
    }
    Ok(())
  }
}