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}