Skip to main content

lychee_lib/checker/wikilink/
index.rs

1use log::info;
2use std::collections::HashMap;
3use std::ffi::OsString;
4use std::path::Path;
5use std::sync::Mutex;
6use std::{path::PathBuf, sync::Arc};
7use walkdir::WalkDir;
8
9/// Indexes a given directory mapping filenames to their corresponding path.
10///
11/// The `WikilinkIndex` recursively checks all subdirectories of the given
12/// base directory mapping any found files to the path where they can be found.
13/// Symlinks are ignored to prevent it from infinite loops.
14#[derive(Clone, Debug)]
15pub(crate) struct WikilinkIndex {
16    filenames: Arc<Mutex<HashMap<OsString, PathBuf>>>,
17    /// Local base directory
18    local_base: PathBuf,
19}
20
21impl WikilinkIndex {
22    pub(crate) fn new(local_base: PathBuf) -> Self {
23        let index = Self {
24            local_base,
25            filenames: Arc::new(Mutex::new(HashMap::new())),
26        };
27        index.start_indexing();
28        index
29    }
30
31    /// Populates the index of the `WikilinkIndex` on startup by walking
32    /// the local base directory, mapping each filename to an absolute filepath.
33    pub(crate) fn start_indexing(&self) {
34        // Start file indexing only if the Base is valid and local
35        info!(
36            "Starting file indexing for wikilinks in {}",
37            self.local_base.display()
38        );
39
40        for entry in WalkDir::new(&self.local_base)
41            // actively ignore symlinks
42            .follow_links(false)
43            .into_iter()
44            .filter_map(Result::ok)
45        {
46            if let Some(filename) = entry.path().file_name() {
47                self.filenames
48                    .lock()
49                    .unwrap()
50                    .insert(filename.to_os_string(), entry.path().to_path_buf());
51            }
52        }
53    }
54
55    /// Checks the index for a filename. Returning the absolute path if the name is found,
56    /// otherwise returning None
57    pub(crate) fn contains_path(&self, path: &Path) -> Option<PathBuf> {
58        self.filenames
59            .lock()
60            .unwrap()
61            .get(path.file_name()?)
62            .cloned()
63    }
64}