mirror of
https://github.com/YGGverse/agate.git
synced 2026-04-08 20:45:29 +00:00
add metadata database
This commit is contained in:
parent
bb7e885143
commit
c916827709
2 changed files with 116 additions and 0 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod metadata;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
once_cell::sync::Lazy,
|
once_cell::sync::Lazy,
|
||||||
percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS},
|
percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS},
|
||||||
|
|
|
||||||
114
src/metadata.rs
Normal file
114
src/metadata.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
/// A struct to store a string of metadata for each file retrieved from
|
||||||
|
/// sidecar files called `.lang`.
|
||||||
|
///
|
||||||
|
/// These sidecar file's lines should have the format
|
||||||
|
/// ```text
|
||||||
|
/// <filename>:<metadata>\n
|
||||||
|
/// ```
|
||||||
|
/// where `<filename>` is only a filename (not a path) of a file that resides
|
||||||
|
/// in the same directory and `<metadata>` is the metadata to be stored.
|
||||||
|
/// Lines that start with optional whitespace and `#` are ignored, as are lines
|
||||||
|
/// that do not fit the basic format.
|
||||||
|
/// Both parts are stripped of any leading and/or trailing whitespace.
|
||||||
|
pub(crate) struct FileOptions {
|
||||||
|
/// Stores the paths of the side files and when they were last read.
|
||||||
|
/// By comparing this to the last write time, we can know if the file
|
||||||
|
/// has changed.
|
||||||
|
databases_read: BTreeMap<PathBuf, SystemTime>,
|
||||||
|
/// Stores the metadata for each file
|
||||||
|
file_meta: BTreeMap<PathBuf, String>,
|
||||||
|
/// The default value to return
|
||||||
|
default: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileOptions {
|
||||||
|
pub(crate) fn new(default: &String) -> Self {
|
||||||
|
Self {
|
||||||
|
databases_read: BTreeMap::new(),
|
||||||
|
file_meta: BTreeMap::new(),
|
||||||
|
default: default.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks wether the database for the respective directory is still
|
||||||
|
/// up to date.
|
||||||
|
/// Will only return true if the database should be (re)read, i.e. it will
|
||||||
|
/// return false if there is no database file in the specified directory.
|
||||||
|
fn check_outdated(&self, db_dir: &PathBuf) -> bool {
|
||||||
|
let mut db = db_dir.clone();
|
||||||
|
db.push(".lang");
|
||||||
|
let db = db.as_path();
|
||||||
|
|
||||||
|
if let Ok(metadata) = db.metadata() {
|
||||||
|
if !metadata.is_file() {
|
||||||
|
// it exists, but it is a directory
|
||||||
|
false
|
||||||
|
} else if let (Ok(modified), Some(last_read)) =
|
||||||
|
(metadata.modified(), self.databases_read.get(db))
|
||||||
|
{
|
||||||
|
// check that it was last modified before the read
|
||||||
|
// if the times are the same, we might have read the old file
|
||||||
|
&modified < last_read
|
||||||
|
} else {
|
||||||
|
// either the filesystem does not support last modified
|
||||||
|
// metadata, so we have to read it again every time; or the
|
||||||
|
// file exists but was not read before, so we have to read it
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the file probably does not exist
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (Re)reads a specific sidecar file that resides in the specified
|
||||||
|
/// directory. The function takes a directory to minimize path
|
||||||
|
/// alterations "on the fly".
|
||||||
|
/// This function will allways try to read the file, even if it is current.
|
||||||
|
fn read_database(&mut self, db_dir: &PathBuf) {
|
||||||
|
let mut db = db_dir.clone();
|
||||||
|
db.push(".lang");
|
||||||
|
let db = db.as_path();
|
||||||
|
|
||||||
|
if let Ok(file) = std::fs::File::open(db) {
|
||||||
|
let r = BufReader::new(file);
|
||||||
|
r.lines()
|
||||||
|
// discard any I/O errors
|
||||||
|
.filter_map(|line| line.ok())
|
||||||
|
// filter out comment lines
|
||||||
|
.filter(|line| !line.trim_start().starts_with("#"))
|
||||||
|
.for_each(|line| {
|
||||||
|
// split line at colon
|
||||||
|
let parts = line.splitn(2, ':').collect::<Vec<_>>();
|
||||||
|
// only continue if line fits the format
|
||||||
|
if parts.len() == 2 {
|
||||||
|
// generate workspace-unique path
|
||||||
|
let mut path = db_dir.clone();
|
||||||
|
path.push(parts[0].trim());
|
||||||
|
self.file_meta.insert(path, parts[1].trim().to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.databases_read
|
||||||
|
.insert(db_dir.clone(), SystemTime::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the metadata for the specified file. This might need to (re)load a
|
||||||
|
/// single sidecar file.
|
||||||
|
/// The file path should consistenly be either absolute or relative to the
|
||||||
|
/// working/content directory. If inconsisten file paths are used, this can
|
||||||
|
/// lead to loading and storing sidecar files multiple times.
|
||||||
|
pub fn get(&mut self, file: PathBuf) -> &str {
|
||||||
|
let dir = file.parent().expect("no parent directory").to_path_buf();
|
||||||
|
if self.check_outdated(&dir) {
|
||||||
|
self.read_database(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.file_meta.get(&file).unwrap_or(&self.default)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue