Skip to main content

hdfs_native/
sync.rs

1//! Synchronous wrappers around the asynchronous HDFS client.
2//!
3//! The sync client owns a Tokio runtime and delegates operations to the async
4//! [`crate::Client`]. This is intended for applications that want blocking APIs
5//! without managing an async runtime directly.
6
7use std::future::Future;
8use std::io::{self, Read, Seek, SeekFrom, Write};
9use std::sync::{Arc, Mutex};
10
11use bytes::Bytes;
12use futures::StreamExt;
13use futures::stream::BoxStream;
14use tokio::runtime::Runtime;
15
16use crate::acl::{AclEntry, AclStatus};
17use crate::client::{self, ContentSummary, FileStatus, WriteOptions};
18use crate::file::{FileReader as AsyncFileReader, FileWriter as AsyncFileWriter};
19use crate::{Result, client::IORuntime};
20
21fn io_error(error: crate::HdfsError) -> io::Error {
22    io::Error::other(error)
23}
24
25/// Builds a new synchronous [`Client`] instance.
26#[derive(Default)]
27pub struct ClientBuilder {
28    inner: client::ClientBuilder,
29}
30
31impl ClientBuilder {
32    /// Create a new [`ClientBuilder`].
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Set the URL to connect to.
38    pub fn with_url(mut self, url: impl Into<String>) -> Self {
39        self.inner = self.inner.with_url(url);
40        self
41    }
42
43    /// Set configs to use for the client.
44    pub fn with_config(
45        mut self,
46        config: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
47    ) -> Self {
48        self.inner = self.inner.with_config(config);
49        self
50    }
51
52    /// Set the configuration directory path to read from.
53    pub fn with_config_dir(mut self, config_dir: impl Into<String>) -> Self {
54        self.inner = self.inner.with_config_dir(config_dir);
55        self
56    }
57
58    /// Set the effective user for the client. If not set, the client will detect user from
59    /// environment variables `HADOOP_USER_NAME` or `HADOOP_PROXY_USER`.
60    pub fn with_user(mut self, user: impl Into<String>) -> Self {
61        self.inner = self.inner.with_user(user);
62        self
63    }
64
65    /// Set the Kerberos principal used by this client.
66    pub fn with_kerberos_principal(mut self, principal: impl Into<String>) -> Self {
67        self.inner = self.inner.with_kerberos_principal(principal);
68        self
69    }
70
71    /// Set the Kerberos keytab used by this client.
72    pub fn with_kerberos_keytab(mut self, keytab: impl Into<String>) -> Self {
73        self.inner = self.inner.with_kerberos_keytab(keytab);
74        self
75    }
76
77    /// Set the Kerberos credential cache used by this client.
78    pub fn with_kerberos_cache(mut self, cache: impl Into<String>) -> Self {
79        self.inner = self.inner.with_kerberos_cache(cache);
80        self
81    }
82
83    /// Create the synchronous [`Client`] from the provided settings.
84    pub fn build(self) -> Result<Client> {
85        let rt = Arc::new(Runtime::new()?);
86        let inner = self
87            .inner
88            .with_io_runtime(IORuntime::from(rt.handle().clone()))
89            .build()?;
90        Ok(Client { inner, rt })
91    }
92}
93
94/// A blocking HDFS client.
95#[derive(Clone, Debug)]
96pub struct Client {
97    inner: client::Client,
98    rt: Arc<Runtime>,
99}
100
101impl Client {
102    fn block_on<F: Future>(&self, future: F) -> F::Output {
103        self.rt.block_on(future)
104    }
105
106    /// Retrieve the file status for the file at `path`.
107    pub fn get_file_info(&self, path: &str) -> Result<FileStatus> {
108        self.block_on(self.inner.get_file_info(path))
109    }
110
111    /// Retrieve all file statuses under `path`.
112    pub fn list_status(&self, path: &str, recursive: bool) -> Result<Vec<FileStatus>> {
113        self.block_on(self.inner.list_status(path, recursive))
114    }
115
116    /// Retrieve a blocking iterator of all files in directories located at `path`.
117    pub fn list_status_iter(&self, path: &str, recursive: bool) -> ListStatusIterator {
118        ListStatusIterator {
119            inner: self.inner.list_status_iter(path, recursive),
120            rt: Arc::clone(&self.rt),
121        }
122    }
123
124    /// Opens a file reader for the file at `path`.
125    pub fn read(&self, path: &str) -> Result<FileReader> {
126        Ok(FileReader {
127            inner: self.block_on(self.inner.read(path))?,
128            rt: Arc::clone(&self.rt),
129        })
130    }
131
132    /// Opens a new file for writing.
133    pub fn create(&self, src: &str, write_options: impl AsRef<WriteOptions>) -> Result<FileWriter> {
134        Ok(FileWriter {
135            inner: self.block_on(self.inner.create(src, write_options))?,
136            rt: Arc::clone(&self.rt),
137        })
138    }
139
140    /// Opens an existing file for appending.
141    pub fn append(&self, src: &str) -> Result<FileWriter> {
142        Ok(FileWriter {
143            inner: self.block_on(self.inner.append(src))?,
144            rt: Arc::clone(&self.rt),
145        })
146    }
147
148    /// Recover a file lease, returning true once the file is closed.
149    ///
150    /// The caller must hold exclusive recovery authority for this path.
151    pub fn recover_lease(&self, src: &str) -> Result<bool> {
152        self.block_on(self.inner.recover_lease(src))
153    }
154
155    /// Create a new directory at `path` with the given permission.
156    pub fn mkdirs(&self, path: &str, permission: u32, create_parent: bool) -> Result<()> {
157        self.block_on(self.inner.mkdirs(path, permission, create_parent))
158    }
159
160    /// Rename `src` to `dst`.
161    pub fn rename(&self, src: &str, dst: &str, overwrite: bool) -> Result<()> {
162        self.block_on(self.inner.rename(src, dst, overwrite))
163    }
164
165    /// Delete the file or directory at `path`.
166    pub fn delete(&self, path: &str, recursive: bool) -> Result<bool> {
167        self.block_on(self.inner.delete(path, recursive))
168    }
169
170    /// Move a file or directory at `path` into the user's trash.
171    pub fn trash(&self, path: &str) -> Result<Option<String>> {
172        self.block_on(self.inner.trash(path))
173    }
174
175    /// Set modified and access times for a file.
176    pub fn set_times(&self, path: &str, mtime: u64, atime: u64) -> Result<()> {
177        self.block_on(self.inner.set_times(path, mtime, atime))
178    }
179
180    /// Optionally set the owner and group for a file.
181    pub fn set_owner(&self, path: &str, owner: Option<&str>, group: Option<&str>) -> Result<()> {
182        self.block_on(self.inner.set_owner(path, owner, group))
183    }
184
185    /// Set permissions for a file.
186    pub fn set_permission(&self, path: &str, permission: u32) -> Result<()> {
187        self.block_on(self.inner.set_permission(path, permission))
188    }
189
190    /// Set replication for a file.
191    pub fn set_replication(&self, path: &str, replication: u32) -> Result<bool> {
192        self.block_on(self.inner.set_replication(path, replication))
193    }
194
195    /// Get a content summary for a file or directory rooted at `path`.
196    pub fn get_content_summary(&self, path: &str) -> Result<ContentSummary> {
197        self.block_on(self.inner.get_content_summary(path))
198    }
199
200    /// Update ACL entries for file or directory at `path`.
201    pub fn modify_acl_entries(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
202        self.block_on(self.inner.modify_acl_entries(path, acl_spec))
203    }
204
205    /// Remove specific ACL entries for file or directory at `path`.
206    pub fn remove_acl_entries(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
207        self.block_on(self.inner.remove_acl_entries(path, acl_spec))
208    }
209
210    /// Remove all default ACL entries for file or directory at `path`.
211    pub fn remove_default_acl(&self, path: &str) -> Result<()> {
212        self.block_on(self.inner.remove_default_acl(path))
213    }
214
215    /// Remove all ACL entries for file or directory at `path`.
216    pub fn remove_acl(&self, path: &str) -> Result<()> {
217        self.block_on(self.inner.remove_acl(path))
218    }
219
220    /// Override ACL entries for file or directory at `path`.
221    pub fn set_acl(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
222        self.block_on(self.inner.set_acl(path, acl_spec))
223    }
224
225    /// Get ACL status for the file or directory at `path`.
226    pub fn get_acl_status(&self, path: &str) -> Result<AclStatus> {
227        self.block_on(self.inner.get_acl_status(path))
228    }
229
230    /// Get all file statuses matching the glob `pattern`.
231    pub fn glob_status(&self, pattern: &str) -> Result<Vec<FileStatus>> {
232        self.block_on(self.inner.glob_status(pattern))
233    }
234}
235
236impl Default for Client {
237    fn default() -> Self {
238        ClientBuilder::new()
239            .build()
240            .expect("Failed to create default client")
241    }
242}
243
244/// A blocking file status iterator.
245pub struct ListStatusIterator {
246    inner: client::ListStatusIterator,
247    rt: Arc<Runtime>,
248}
249
250impl Iterator for ListStatusIterator {
251    type Item = Result<FileStatus>;
252
253    fn next(&mut self) -> Option<Self::Item> {
254        self.rt.block_on(self.inner.next())
255    }
256}
257
258/// A blocking file reader.
259pub struct FileReader {
260    inner: AsyncFileReader,
261    rt: Arc<Runtime>,
262}
263
264impl FileReader {
265    /// Returns the total size of the file.
266    pub fn file_length(&self) -> usize {
267        self.inner.file_length()
268    }
269
270    /// Returns the remaining bytes left based on the current cursor position.
271    pub fn remaining(&self) -> usize {
272        self.inner.remaining()
273    }
274
275    /// Sets the cursor position.
276    pub fn set_position(&mut self, pos: usize) {
277        self.inner.set_position(pos);
278    }
279
280    /// Returns the current cursor position in the file.
281    pub fn tell(&self) -> usize {
282        self.inner.tell()
283    }
284
285    /// Read up to `len` bytes, advancing the internal position.
286    pub fn read_bytes(&mut self, len: usize) -> Result<Bytes> {
287        self.rt.block_on(self.inner.read_bytes(len))
288    }
289
290    /// Read up to `buf.len()` bytes into the provided slice.
291    pub fn read_into(&mut self, buf: &mut [u8]) -> Result<usize> {
292        self.rt.block_on(self.inner.read_into(buf))
293    }
294
295    /// Read up to `len` bytes starting at `offset`.
296    pub fn read_range(&self, offset: usize, len: usize) -> Result<Bytes> {
297        self.rt.block_on(self.inner.read_range(offset, len))
298    }
299
300    /// Read file data into an existing buffer.
301    pub fn read_range_buf(&self, buf: &mut [u8], offset: usize) -> Result<()> {
302        self.rt.block_on(self.inner.read_range_buf(buf, offset))
303    }
304
305    /// Return a blocking stream of `Bytes` objects containing the file content.
306    pub fn read_range_stream(&self, offset: usize, len: usize) -> FileReadStream {
307        FileReadStream {
308            inner: Mutex::new(self.inner.read_range_stream(offset, len).boxed()),
309            rt: Arc::clone(&self.rt),
310        }
311    }
312}
313
314impl Read for FileReader {
315    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
316        self.read_into(buf).map_err(io_error)
317    }
318}
319
320impl Seek for FileReader {
321    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
322        let file_length = self.file_length() as i128;
323        let current = self.tell() as i128;
324        let new_pos = match pos {
325            SeekFrom::Start(pos) => i128::from(pos),
326            SeekFrom::End(offset) => file_length + i128::from(offset),
327            SeekFrom::Current(offset) => current + i128::from(offset),
328        };
329
330        if new_pos < 0 || new_pos > file_length {
331            return Err(io::Error::new(
332                io::ErrorKind::InvalidInput,
333                "cannot seek outside of file bounds",
334            ));
335        }
336
337        self.inner.set_position(new_pos as usize);
338        Ok(new_pos as u64)
339    }
340}
341
342/// A blocking stream of file bytes.
343pub struct FileReadStream {
344    inner: Mutex<BoxStream<'static, Result<Bytes>>>,
345    rt: Arc<Runtime>,
346}
347
348impl Iterator for FileReadStream {
349    type Item = Result<Bytes>;
350
351    fn next(&mut self) -> Option<Self::Item> {
352        self.rt.block_on(self.inner.lock().unwrap().next())
353    }
354}
355
356/// A blocking file writer.
357pub struct FileWriter {
358    inner: AsyncFileWriter,
359    rt: Arc<Runtime>,
360}
361
362impl FileWriter {
363    /// Write bytes to the file.
364    pub fn write_bytes(&mut self, buf: Bytes) -> Result<usize> {
365        self.rt.block_on(self.inner.write_bytes(buf))
366    }
367
368    /// Persist all data written so far while keeping the writer open.
369    pub fn hsync(&mut self) -> Result<()> {
370        self.rt.block_on(self.inner.hsync())
371    }
372
373    /// Close the file writer.
374    pub fn close(&mut self) -> Result<()> {
375        self.rt.block_on(self.inner.close())
376    }
377}
378
379impl Write for FileWriter {
380    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
381        self.write_bytes(Bytes::copy_from_slice(buf))
382            .map_err(io_error)
383    }
384
385    fn flush(&mut self) -> io::Result<()> {
386        Ok(())
387    }
388}