netidx-archive 0.32.0

netidx archive file format
Documentation
use crate::{config::Config, logfile_collection::parse_name};
use anyhow::Result;
use chrono::prelude::*;
use log::{debug, info, warn};
use std::{cmp::Ordering, path::PathBuf, sync::Arc};

use super::to_name;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum File {
    Head,
    Historical(DateTime<Utc>),
}

impl PartialOrd for File {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (File::Head, File::Head) => Some(Ordering::Equal),
            (File::Head, File::Historical(_)) => Some(Ordering::Greater),
            (File::Historical(_), File::Head) => Some(Ordering::Less),
            (File::Historical(d0), File::Historical(d1)) => d0.partial_cmp(d1),
        }
    }
}

impl Ord for File {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

impl File {
    fn read(config: &Config, shard: &str) -> Result<Vec<File>> {
        let mut files = vec![];
        {
            let path = config.archive_directory.join(shard);
            std::fs::create_dir_all(&path)?;
            for dir in std::fs::read_dir(&path)? {
                let dir = dir?;
                let typ = dir.file_type()?;
                if typ.is_file() || typ.is_symlink() {
                    let name = dir.file_name();
                    let name = name.to_string_lossy();
                    if name == "current" {
                        files.push(File::Head);
                    } else if let Ok(ts) = parse_name(&name) {
                        files.push(File::Historical(ts));
                    }
                }
            }
        }
        debug!("would run list, cmd config {:?}", &config.archive_cmds);
        if let Some(cmds) = &config.archive_cmds {
            use std::process::Command;
            info!("running list command");
            let args = cmds.list.1.iter().cloned().map(|s| s.replace("{shard}", shard));
            match Command::new(&cmds.list.0).args(args).output() {
                Err(e) => warn!("failed to run list command {}", e),
                Ok(o) if !o.status.success() => warn!("list command failed {:?}", o),
                Ok(output) => {
                    let stdout = String::from_utf8_lossy(&output.stdout);
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    if stderr.len() > 0 {
                        warn!("list stderr {}", stderr);
                    }
                    info!("list succeeded with {}", &stdout);
                    for name in stdout.split("\n") {
                        match parse_name(&name) {
                            Err(e) => {
                                warn!("failed to parse list ts \'{}\', {}", name, e)
                            }
                            Ok(ts) => files.push(File::Historical(ts)),
                        }
                    }
                }
            };
        }
        files.sort();
        files.dedup();
        Ok(files)
    }

    pub(super) fn path(&self, base: &PathBuf, shard: &str) -> PathBuf {
        match self {
            File::Head => base.join(shard).join("current"),
            File::Historical(h) => base.join(shard).join(to_name(h)),
        }
    }
}

#[derive(Debug, Clone)]
pub struct ArchiveIndex(Arc<Vec<File>>);

impl ArchiveIndex {
    pub fn new(config: &Config, shard: &str) -> Result<Self> {
        Ok(Self(Arc::new(File::read(&config, shard)?)))
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn first(&self) -> File {
        if self.0.len() == 0 {
            File::Head
        } else {
            *self.0.first().unwrap()
        }
    }

    pub fn last(&self) -> File {
        if self.0.len() == 0 {
            File::Head
        } else {
            *self.0.last().unwrap()
        }
    }

    pub fn find(&self, ts: DateTime<Utc>) -> File {
        if self.0.len() == 0 {
            File::Head
        } else {
            match self.0.binary_search(&File::Historical(ts)) {
                Err(i) => self.0[i],
                Ok(i) => {
                    if i + 1 < self.0.len() {
                        self.0[i + 1]
                    } else {
                        File::Head
                    }
                }
            }
        }
    }

    pub fn next(&self, cur: File) -> File {
        if self.0.len() == 0 {
            File::Head
        } else {
            match self.0.binary_search(&cur) {
                Err(i) | Ok(i) => {
                    if i + 1 < self.0.len() {
                        self.0[i + 1]
                    } else {
                        self.0[i]
                    }
                }
            }
        }
    }

    pub fn prev(&self, cur: File) -> File {
        if self.0.len() == 0 {
            File::Head
        } else {
            match self.0.binary_search(&cur) {
                Err(i) | Ok(i) => {
                    if i > 0 {
                        self.0[i - 1]
                    } else {
                        self.0[i]
                    }
                }
            }
        }
    }
}