post_archiver/
file_meta.rs

1use serde::{Deserialize, Serialize};
2
3use serde_json::Value;
4#[cfg(feature = "typescript")]
5use ts_rs::TS;
6
7use std::{collections::HashMap, hash::Hash, path::PathBuf};
8
9use crate::id::{FileMetaId, PostId};
10
11/// The number of posts in one chunk.
12pub const POSTS_PRE_CHUNK: u32 = 2048;
13
14/// Metadata for a file in the system with hierarchical path organization
15#[cfg_attr(feature = "typescript", derive(TS))]
16#[cfg_attr(feature = "typescript", ts(export))]
17#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
18pub struct FileMeta {
19    pub id: FileMetaId,
20    pub filename: String,
21    pub post: PostId,
22    pub mime: String,
23    #[cfg_attr(feature = "typescript", ts(type = "Record<string, any>"))]
24    pub extra: HashMap<String, Value>,
25}
26
27impl FileMeta {
28    /// Returns relative path to the file.  
29    /// it will be `<chunk>/<index>/<filename>`.  
30    /// the `<chunk>` is `postId / POSTS_PRE_CHUNK`,
31    /// the `<index>` is `postId % POSTS_PRE_CHUNK`,
32    /// [`POSTS_PRE_CHUNK`] is a constant that defines how many posts are in one chunk. (default is 2048).
33    ///
34    /// # Examples
35    /// ```rust
36    /// use post_archiver::{FileMeta, AuthorId, PostId, FileMetaId};
37    /// use std::collections::HashMap;
38    /// use std::path::PathBuf;
39    ///
40    /// // You should never create a FileMeta struct
41    /// let file_meta = FileMeta {
42    ///     id: FileMetaId::new(6),
43    ///     post: PostId::new(2049),
44    ///     filename: "example.txt".to_string(),
45    ///     mime: "text/plain".to_string(),
46    ///     extra: HashMap::new(),
47    /// };
48    ///
49    /// let path = file_meta.path();
50    /// assert_eq!(path.to_str(), Some("1/1/example.txt"));
51    /// ```
52    pub fn path(&self) -> PathBuf {
53        let id = self.post.raw();
54        let chunk = id / POSTS_PRE_CHUNK;
55        let index = id % POSTS_PRE_CHUNK;
56        PathBuf::from(chunk.to_string())
57            .join(index.to_string())
58            .join(&self.filename)
59    }
60}
61
62impl Hash for FileMeta {
63    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64        self.id.hash(state);
65        self.post.hash(state);
66        self.filename.hash(state);
67        self.mime.hash(state);
68        // We don't hash `extra` because it can be large.
69    }
70}
71
72#[cfg(feature = "utils")]
73crate::utils::macros::as_table!(
74    FileMeta {
75        id: "id",
76        post: "post",
77        filename: "filename",
78        mime: "mime",
79        extra: "extra" => json,
80    }
81);