mod hash;
pub mod id;
pub mod markdown_import;
pub mod progress;
pub mod time;
pub use hash::{ContentHashable, content_hash, content_hash_from_parts};
pub use id::{
IdConfig, IdGenerator, IdResolver, MatchType, ParsedId, ResolvedId, ResolverConfig, child_id,
find_matching_ids, generate_id, id_depth, is_child_id, is_valid_id_format, normalize_id,
parse_id, resolve_id, validate_prefix,
};
use std::env;
use std::fs::{self, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
const LAST_TOUCHED_FILE: &str = "last-touched";
pub const BEADS_CACHE_DIR_ENV: &str = "BEADS_CACHE_DIR";
#[must_use]
pub fn resolve_cache_dir(beads_dir: &Path) -> PathBuf {
if let Ok(cache_dir) = env::var(BEADS_CACHE_DIR_ENV) {
let path = PathBuf::from(&cache_dir);
if !cache_dir.is_empty() {
return path;
}
}
beads_dir.to_path_buf()
}
#[must_use]
pub fn last_touched_path(beads_dir: &Path) -> PathBuf {
resolve_cache_dir(beads_dir).join(LAST_TOUCHED_FILE)
}
const DB_FILE: &str = "beads.db";
#[must_use]
pub fn db_path(beads_dir: &Path) -> PathBuf {
resolve_cache_dir(beads_dir).join(DB_FILE)
}
pub fn set_last_touched_id(beads_dir: &Path, id: &str) {
let path = last_touched_path(beads_dir);
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let mut options = OpenOptions::new();
options.create(true).write(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
if let Ok(mut file) = options.open(path) {
let _ = writeln!(file, "{id}");
}
}
#[must_use]
pub fn get_last_touched_id(beads_dir: &Path) -> String {
let path = last_touched_path(beads_dir);
let mut contents = String::new();
if let Ok(mut file) = fs::File::open(path)
&& file.read_to_string(&mut contents).is_ok()
{
return contents.lines().next().unwrap_or("").trim().to_string();
}
String::new()
}
pub fn clear_last_touched(beads_dir: &Path) {
let path = last_touched_path(beads_dir);
let _ = fs::remove_file(path);
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_set_get_clear_last_touched() {
let temp = TempDir::new().expect("temp dir");
let beads_dir = temp.path().join(".beads");
fs::create_dir(&beads_dir).expect("create .beads");
assert_eq!(get_last_touched_id(&beads_dir), "");
set_last_touched_id(&beads_dir, "bd-abc123");
assert_eq!(get_last_touched_id(&beads_dir), "bd-abc123");
clear_last_touched(&beads_dir);
assert_eq!(get_last_touched_id(&beads_dir), "");
}
#[cfg(unix)]
#[test]
fn test_last_touched_permissions() {
use std::os::unix::fs::PermissionsExt;
let temp = TempDir::new().expect("temp dir");
let beads_dir = temp.path().join(".beads");
fs::create_dir(&beads_dir).expect("create .beads");
set_last_touched_id(&beads_dir, "bd-abc123");
let metadata = fs::metadata(last_touched_path(&beads_dir)).expect("metadata");
assert_eq!(metadata.permissions().mode() & 0o777, 0o600);
}
#[test]
fn test_set_last_touched_creates_parent_dir() {
let temp = TempDir::new().expect("temp dir");
let cache_dir = temp.path().join("nested").join("cache");
let path = cache_dir.join(LAST_TOUCHED_FILE);
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
assert!(cache_dir.exists(), "parent dir should be created");
}
}