1use camino::Utf8PathBuf;
5use std::collections::HashMap;
6use std::sync::{LazyLock, Mutex};
7
8static WRITER_LOCATIONS: LazyLock<Mutex<HashMap<WriterLocation, usize>>> =
9 LazyLock::new(Default::default);
10
11#[derive(Hash, PartialEq, Eq, Debug, Clone)]
13pub struct WriterLocation {
14 pub directory: Utf8PathBuf,
16 pub pattern: String,
19}
20
21pub struct WriterRegistration {
25 location: WriterLocation,
26}
27
28impl WriterRegistration {
29 pub fn new(directory: Utf8PathBuf, suffix: Option<&str>) -> Self {
32 let location = WriterLocation {
33 directory,
34 pattern: pattern_for_suffix(suffix),
35 };
36 *WRITER_LOCATIONS
37 .lock()
38 .expect("jsonl writer registry mutex poisoned")
39 .entry(location.clone())
40 .or_insert(0) += 1;
41 Self { location }
42 }
43}
44
45impl Drop for WriterRegistration {
46 fn drop(&mut self) {
47 let mut locations = WRITER_LOCATIONS
48 .lock()
49 .expect("jsonl writer registry mutex poisoned");
50 if let Some(count) = locations.get_mut(&self.location) {
51 *count -= 1;
52 if *count == 0 {
53 locations.remove(&self.location);
54 }
55 }
56 }
57}
58
59pub fn active_writer_locations() -> Vec<WriterLocation> {
62 WRITER_LOCATIONS
63 .lock()
64 .expect("jsonl writer registry mutex poisoned")
65 .keys()
66 .cloned()
67 .collect()
68}
69
70fn pattern_for_suffix(suffix: Option<&str>) -> String {
73 const GLOB_META: &[char] = &['\\', '*', '?', '[', ']', '{', '}'];
74 match suffix {
75 None => "*".to_string(),
76 Some(suffix) => {
77 let mut pattern = String::with_capacity(1 + suffix.len());
78 pattern.push('*');
79 for c in suffix.chars() {
80 if GLOB_META.contains(&c) {
81 pattern.push('\\');
82 }
83 pattern.push(c);
84 }
85 pattern
86 }
87 }
88}
89
90#[cfg(test)]
91mod test {
92 use super::*;
93
94 #[test]
95 fn suffix_to_pattern() {
96 k9::assert_equal!(pattern_for_suffix(None), "*");
97 k9::assert_equal!(pattern_for_suffix(Some(".log")), "*.log");
98 k9::assert_equal!(pattern_for_suffix(Some("-v1[a]*")), "*-v1\\[a\\]\\*");
101 }
102}