hdrs/
metadata.rs

1use std::ffi::CStr;
2use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4use hdfs_sys::*;
5
6/// Metadata of a path.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Metadata {
9    /// the name of the file, like `file:/path/to/file`
10    path: String,
11    /// the size of the file in bytes
12    size: i64,
13    /// file or directory
14    kind: u32,
15    /// the permissions associated with the file
16    permissions: i16,
17    /// the count of replicas
18    replication: i16,
19    /// the block size for the file
20    block_size: i64,
21    /// the owner of the file
22    owner: String,
23    /// the group associated with the file
24    group: String,
25    /// the last modification time for the file in seconds
26    last_mod: i64,
27    /// the last access time for the file in seconds
28    last_access: i64,
29}
30
31impl Metadata {
32    /// the path of the file, like `/path/to/file`
33    ///
34    /// # Notes
35    ///
36    /// Hadoop has [restrictions](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/filesystem/introduction.html) of path name:
37    ///
38    /// - A Path is comprised of Path elements separated by "/".
39    /// - A path element is a unicode string of 1 or more characters.
40    /// - Path element MUST NOT include the characters ":" or "/".
41    /// - Path element SHOULD NOT include characters of ASCII/UTF-8 value 0-31 .
42    /// - Path element MUST NOT be "." or ".."
43    /// - Note also that the Azure blob store documents say that paths SHOULD NOT use a trailing "." (as their .NET URI class strips it).
44    /// - Paths are compared based on unicode code-points.
45    /// - Case-insensitive and locale-specific comparisons MUST NOT not be used.
46    pub fn path(&self) -> &str {
47        &self.path
48    }
49
50    /// the size of the file in bytes
51    ///
52    /// Metadata is not a collection, so we will not provide `is_empty`.
53    /// Keep the same style with `std::fs::File`
54    #[allow(clippy::len_without_is_empty)]
55    pub fn len(&self) -> u64 {
56        self.size as u64
57    }
58
59    /// file or directory
60    pub fn is_dir(&self) -> bool {
61        self.kind == tObjectKind_kObjectKindDirectory
62    }
63
64    /// file or directory
65    pub fn is_file(&self) -> bool {
66        self.kind == tObjectKind_kObjectKindFile
67    }
68
69    /// the permissions associated with the file
70    pub fn permissions(&self) -> i16 {
71        self.permissions
72    }
73
74    /// the count of replicas
75    pub fn replication(&self) -> i16 {
76        self.replication
77    }
78
79    /// the block size for the file
80    pub fn block_size(&self) -> i64 {
81        self.block_size
82    }
83
84    /// the owner of the file
85    pub fn owner(&self) -> &str {
86        &self.owner
87    }
88
89    /// the group associated with the file
90    pub fn group(&self) -> &str {
91        &self.group
92    }
93
94    /// the last modification time for the file in seconds
95    pub fn modified(&self) -> SystemTime {
96        UNIX_EPOCH
97            .checked_add(Duration::from_secs(self.last_mod as u64))
98            .expect("must be valid SystemTime")
99    }
100
101    /// the last access time for the file in seconds
102    pub fn accessed(&self) -> SystemTime {
103        UNIX_EPOCH
104            .checked_add(Duration::from_secs(self.last_access as u64))
105            .expect("must be valid SystemTime")
106    }
107}
108
109impl From<hdfsFileInfo> for Metadata {
110    fn from(hfi: hdfsFileInfo) -> Self {
111        Self {
112            path: {
113                let p = unsafe {
114                    CStr::from_ptr(hfi.mName)
115                        .to_str()
116                        .expect("hdfs owner must be valid utf-8")
117                };
118
119                match p.find(':') {
120                    None => p.to_string(),
121                    Some(idx) => match &p[..idx] {
122                        // `file:/path/to/file` => `/path/to/file`
123                        "file" => p[idx + 1..].to_string(),
124                        // `hdfs://127.0.0.1:9000/path/to/file` => `/path/to/file`
125                        _ => {
126                            // length of `hdfs://`
127                            let scheme = idx + 2;
128                            // the first occur of `/` in `127.0.0.1:9000/path/to/file`
129                            let endpoint = &p[scheme + 1..]
130                                .find('/')
131                                .expect("hdfs must returns an absolute path");
132                            p[scheme + endpoint + 1..].to_string()
133                        }
134                    },
135                }
136            },
137            size: hfi.mSize,
138            kind: hfi.mKind,
139            permissions: hfi.mPermissions,
140            replication: hfi.mReplication,
141            block_size: hfi.mBlockSize,
142            owner: unsafe {
143                CStr::from_ptr(hfi.mOwner)
144                    .to_str()
145                    .expect("hdfs owner must be valid utf-8")
146                    .into()
147            },
148            group: unsafe {
149                CStr::from_ptr(hfi.mGroup)
150                    .to_str()
151                    .expect("hdfs owner must be valid utf-8")
152                    .into()
153            },
154            last_mod: hfi.mLastMod,
155            last_access: hfi.mLastAccess,
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use std::ffi::CString;
163
164    use super::*;
165
166    #[test]
167    fn test_from_hdfs_file_info() -> anyhow::Result<()> {
168        let cases = vec![
169            (
170                hdfsFileInfo {
171                    mKind: 0,
172                    mName: CString::new("file:/path/to/file")?.into_raw(),
173                    mLastMod: 0,
174                    mSize: 123,
175                    mReplication: 0,
176                    mBlockSize: 0,
177                    mOwner: CString::new("xuanwo")?.into_raw(),
178                    mGroup: CString::new("xuanwo")?.into_raw(),
179                    mPermissions: 0,
180                    mLastAccess: 0,
181                },
182                Metadata {
183                    path: "/path/to/file".into(),
184                    size: 123,
185                    kind: 0,
186                    permissions: 0,
187                    replication: 0,
188                    block_size: 0,
189                    owner: "xuanwo".into(),
190                    group: "xuanwo".into(),
191                    last_mod: 0,
192                    last_access: 0,
193                },
194            ),
195            (
196                hdfsFileInfo {
197                    mKind: 0,
198                    mName: CString::new("hdfs://127.0.0.1:9000/path/to/file")?.into_raw(),
199                    mLastMod: 455,
200                    mSize: 0,
201                    mReplication: 0,
202                    mBlockSize: 0,
203                    mOwner: CString::new("xuanwo")?.into_raw(),
204                    mGroup: CString::new("xuanwo")?.into_raw(),
205                    mPermissions: 0,
206                    mLastAccess: 0,
207                },
208                Metadata {
209                    path: "/path/to/file".into(),
210                    size: 0,
211                    kind: 0,
212                    permissions: 0,
213                    replication: 0,
214                    block_size: 0,
215                    owner: "xuanwo".into(),
216                    group: "xuanwo".into(),
217                    last_mod: 455,
218                    last_access: 0,
219                },
220            ),
221            (
222                hdfsFileInfo {
223                    mKind: 0,
224                    mName: CString::new("/path/to/file")?.into_raw(),
225                    mLastMod: 455,
226                    mSize: 0,
227                    mReplication: 0,
228                    mBlockSize: 0,
229                    mOwner: CString::new("xuanwo")?.into_raw(),
230                    mGroup: CString::new("xuanwo")?.into_raw(),
231                    mPermissions: 0,
232                    mLastAccess: 0,
233                },
234                Metadata {
235                    path: "/path/to/file".into(),
236                    size: 0,
237                    kind: 0,
238                    permissions: 0,
239                    replication: 0,
240                    block_size: 0,
241                    owner: "xuanwo".into(),
242                    group: "xuanwo".into(),
243                    last_mod: 455,
244                    last_access: 0,
245                },
246            ),
247        ];
248
249        for case in cases {
250            let meta = Metadata::from(case.0);
251
252            assert_eq!(meta, case.1);
253        }
254
255        Ok(())
256    }
257}