symlink support

This commit is contained in:
Robin Appelman 2025-10-13 18:30:48 +02:00
commit 1269e95b6f
2 changed files with 41 additions and 9 deletions

View file

@ -8,6 +8,8 @@ use thiserror::Error;
#[derive(Debug, Deserialize)]
pub struct GaltonConfig {
#[serde(default)]
pub watch: WatchConfig,
#[serde(default)]
pub rule: Vec<Rule>,
}
@ -26,15 +28,26 @@ impl GaltonConfig {
}
}
fn normalize_path(path: String) -> String {
if let Some(suffix) = path.strip_prefix("~/") {
#[derive(Debug, Deserialize, Default)]
pub struct WatchConfig {
symlink: Option<String>,
}
impl WatchConfig {
pub fn symlink(&self) -> Option<String> {
self.symlink.as_deref().map(normalize_path)
}
}
fn normalize_path<P: Into<String> + AsRef<str>>(path: P) -> String {
if let Some(suffix) = path.as_ref().strip_prefix("~/") {
let home = home_dir().unwrap_or_default();
home.join(suffix)
.into_os_string()
.into_string()
.expect("non utf8 home directory")
} else {
path
path.into()
}
}

View file

@ -10,6 +10,7 @@ use notify_debouncer_full::notify::{EventKind, RecursiveMode};
use notify_debouncer_full::{new_debouncer, DebounceEventResult};
use std::fs::{copy, create_dir_all, remove_file, rename};
use std::io::ErrorKind;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
@ -66,6 +67,7 @@ fn main() -> MainResult {
}
Commands::Watch { path, recursive } => {
let rules = config.rule;
let symlink = config.watch.symlink();
let (tx, rx) = channel();
let mut watcher = new_debouncer(Duration::from_secs(1), None, tx)?;
watcher.watch(
@ -84,7 +86,7 @@ fn main() -> MainResult {
}
})?;
for res in rx {
handle_watch_events(res, &rules);
handle_watch_event(res, &rules, symlink.as_deref());
}
}
}
@ -92,7 +94,7 @@ fn main() -> MainResult {
Ok(())
}
fn handle_watch_events(result: DebounceEventResult, rules: &[Rule]) {
fn handle_watch_event(result: DebounceEventResult, rules: &[Rule], link_target: Option<&str>) {
match result {
Ok(events) => {
for event in events {
@ -103,7 +105,22 @@ fn handle_watch_events(result: DebounceEventResult, rules: &[Rule]) {
sleep(Duration::from_millis(200));
match FileInfo::load(path) {
Ok(file) => {
handle_file(&file, rules);
if let Some(target) = handle_file(&file, rules) {
if let Some(link_target) = link_target {
match symlink(&target, link_target) {
Ok(()) => {
info!(
to = target,
from = link_target,
"created symlink"
);
}
Err(error) => {
error!(%error, "failed to link target");
}
}
}
}
}
Err(error) => {
error!(%error, "failed to load file info");
@ -132,10 +149,10 @@ fn match_file(file: &FileInfo, rules: &[Rule]) -> Option<RuleMatch> {
}
#[instrument(skip_all, fields(file = file.path))]
fn handle_file(file: &FileInfo, rules: &[Rule]) {
fn handle_file(file: &FileInfo, rules: &[Rule]) -> Option<String> {
let Some(result) = match_file(file, rules) else {
info!("no matches");
return;
return None;
};
let parent = result.target.as_deref().unwrap_or_else(|| file.parent());
@ -143,7 +160,7 @@ fn handle_file(file: &FileInfo, rules: &[Rule]) {
if let Err(error) = create_dir_all(parent) {
error!(%error, "failed to create target directory");
return;
return None;
}
let target = format!("{parent}/{name}");
@ -151,9 +168,11 @@ fn handle_file(file: &FileInfo, rules: &[Rule]) {
match cross_storage_move(&file.path, &target) {
Ok(()) => {
info!(target, "moved file");
Some(target)
}
Err(error) => {
info!(target, ?error, "failed to moved file");
None
}
}
}