quickterm 0.1.0

A small drop-down terminal for Sway/i3 IPC
Documentation
use std::collections::BTreeSet;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;

use fs2::FileExt;

use crate::error::QuicktermError;

pub fn validate_history(loaded: &[String], known_shells: &BTreeSet<String>) -> Option<Vec<String>> {
    let loaded_set: BTreeSet<String> = loaded.iter().cloned().collect();
    if &loaded_set == known_shells {
        Some(loaded.to_vec())
    } else {
        None
    }
}

pub fn reorder_shells(shells: Vec<String>, selected: &str) -> Vec<String> {
    std::iter::once(selected.to_string())
        .chain(shells.into_iter().filter(|name| name != selected))
        .collect()
}

pub struct LockedHistoryFile {
    file: File,
}

impl LockedHistoryFile {
    pub fn open(path: &Path) -> Result<Self, QuicktermError> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }

        let file = OpenOptions::new()
            .create(true)
            .truncate(false)
            .read(true)
            .write(true)
            .open(path)?;

        file.lock_exclusive()?;
        Ok(Self { file })
    }

    pub fn read_order(&mut self) -> Result<Option<Vec<String>>, QuicktermError> {
        self.file.seek(SeekFrom::Start(0))?;
        let mut buf = String::new();
        self.file.read_to_string(&mut buf)?;

        if buf.trim().is_empty() {
            return Ok(None);
        }

        match serde_json::from_str(&buf) {
            Ok(loaded) => Ok(Some(loaded)),
            Err(_) => Ok(None),
        }
    }

    pub fn write_order(&mut self, shells: &[String]) -> Result<(), QuicktermError> {
        self.file.set_len(0)?;
        self.file.seek(SeekFrom::Start(0))?;
        serde_json::to_writer(&mut self.file, shells)?;
        self.file.flush()?;
        Ok(())
    }
}