wayver's git archive


an obsidian renderer
git clone https://git.wayver.dev/sable

sable-vault/src/file.rs@337ba67f65eaa17b44e371af7c0f0c761d6aa914

raw
Date Commit Message Author Files + -
2026-02-23 01:55 initial mvp wayverd 139 17808 0
...

1use petgraph::graph::NodeIndex;
2
3use crate::{
4    ItemPathBuf,
5    note::{Note, NoteError},
6};
7
8static AUDIO_EXTS: &[&str] = &["3gp", "flac", "m4a", "mp3", "ogg", "opus", "wav"];
9static DATA_EXTS: &[&str] = &["json", "toml", "yaml"];
10static IMAGE_EXTS: &[&str] = &["bmp", "gif", "png", "jpeg", "jpg", "svg", "webp"];
11static VIDEO_EXTS: &[&str] = &["avi", "mkv", "mov", "mp4", "ogv", "webm"];
12
13static BASE_EXTS: &[&str] = &["base"];
14static CANVAS_EXTS: &[&str] = &["canvas"];
15static NOTE_EXTS: &[&str] = &["adoc", "gem", "md", "org"];
16
17static HTML_EXTS: &[&str] = &["html", "htm"];
18
19/// All the supported types of files that can exist in a Vault.
20#[derive(Debug, Clone, Hash, PartialEq, Eq)]
21pub enum FileKind {
22    /// An audio file.
23    ///
24    /// Supported file extensions: `3gp`, `flac`, `m4a`, `mp3`, `ogg`, `opus`, `wav`.
25    Audio,
26    /// A generic data file.
27    ///
28    /// Supported file extensions: `json`, `toml`, `yaml`.
29    Data,
30    /// An image file.
31    ///
32    /// Supported file extensions: `bmp`, `gif`, `png`, `jpeg`, `jpg`, `svg`, `webp`.
33    Image,
34    /// A video file.
35    ///
36    /// Supported file extensions: `avi`, `mkv`, `mov`, `mp4`, `ogv`, `webm`.
37    Video,
38
39    /// A Obsidian Base.
40    Base,
41    /// A Obsidian Canvas.
42    Canvas,
43    /// A Obsidian Note.
44    Note,
45
46    /// An HTML file.
47    ///
48    /// Supported file extensions: `html`, `htm`.
49    Html,
50
51    /// An unknown (unsupport) file type.
52    Unknown(Option<String>),
53}
54
55impl FileKind {
56    /// Returns `true` if the file kind is [`FileKind::Audio`], [`FileKind::Image`], or [`FileKind::Video`].
57    #[must_use]
58    pub const fn is_asset(&self) -> bool {
59        self.is_audio() || self.is_image() || self.is_video()
60    }
61
62    /// Returns `true` if the file kind is [`FileKind::Audio`].
63    #[must_use]
64    pub const fn is_audio(&self) -> bool {
65        matches!(self, Self::Audio)
66    }
67
68    /// Returns `true` if the file kind is [`FileKind::Data`].
69    #[must_use]
70    pub const fn is_data(&self) -> bool {
71        matches!(self, Self::Data)
72    }
73
74    /// Returns `true` if the file kind is [`FileKind::Image`].
75    #[must_use]
76    pub const fn is_image(&self) -> bool {
77        matches!(self, Self::Image)
78    }
79
80    /// Returns `true` if the file kind is [`FileKind::Video`].
81    #[must_use]
82    pub const fn is_video(&self) -> bool {
83        matches!(self, Self::Video)
84    }
85
86    /// Returns `true` if the file kind is [`FileKind::Base`].
87    #[must_use]
88    pub const fn is_base(&self) -> bool {
89        matches!(self, Self::Base)
90    }
91
92    /// Returns `true` if the file kind is [`FileKind::Canvas`].
93    #[must_use]
94    pub const fn is_canvas(&self) -> bool {
95        matches!(self, Self::Canvas)
96    }
97
98    /// Returns `true` if the file kind is [`FileKind::Note`].
99    #[must_use]
100    pub const fn is_note(&self) -> bool {
101        matches!(self, Self::Note)
102    }
103
104    /// Returns `true` if the file kind is [`FileKind::Html`].
105    #[must_use]
106    pub const fn is_html(&self) -> bool {
107        matches!(self, Self::Html)
108    }
109}
110
111/// A Vault file.
112///
113/// This is the base entry of all files in a Vault.
114/// Bases, Canvases, and Note are all converted from this.
115#[derive(Debug, Clone, Hash, PartialEq, Eq)]
116pub struct File {
117    /// The path of the file.
118    pub path: ItemPathBuf,
119
120    /// The kind of file this is.
121    ///
122    /// [`FileKind::Base`], [`FileKind::Canvas`], and [`FileKind::Note`] are all converted to their respected structs.
123    /// As such any accessable file will never have those as a kind.
124    pub kind: FileKind,
125}
126
127impl File {
128    /// Create a new [`File`], getting the [`FileKind`] from the path extension.
129    #[must_use]
130    pub fn from_path(path: ItemPathBuf) -> Self {
131        let kind = path
132            .extension()
133            .map_or(FileKind::Unknown(None), |ext| match ext {
134                _ if AUDIO_EXTS.contains(&ext) => FileKind::Audio,
135                _ if DATA_EXTS.contains(&ext) => FileKind::Data,
136                _ if IMAGE_EXTS.contains(&ext) => FileKind::Image,
137                _ if VIDEO_EXTS.contains(&ext) => FileKind::Video,
138
139                _ if BASE_EXTS.contains(&ext) => FileKind::Base,
140                _ if CANVAS_EXTS.contains(&ext) => FileKind::Canvas,
141                _ if NOTE_EXTS.contains(&ext) => FileKind::Note,
142
143                _ if HTML_EXTS.contains(&ext) => FileKind::Html,
144
145                _ => FileKind::Unknown(Some(ext.to_string())),
146            });
147
148        if matches!(&kind, FileKind::Unknown(_)) {
149            tracing::warn!(path=%path.relative, "unknown file type");
150        }
151
152        Self { path, kind }
153    }
154
155    /// Convert this file referencce into a [`Note`].
156    ///
157    /// # Errors
158    ///
159    /// This will error if it failed to read the note from the file system,
160    ///  its unable to deserialize the frontmatter,
161    /// or it was unable to get file system metadata of the note.
162    pub fn into_note(self, index: NodeIndex) -> Result<Note, NoteError> {
163        Note::from_path(self.path, index)
164    }
165}
166