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    /// Create a new directory at `path` with the given permission.
149    pub fn mkdirs(&self, path: &str, permission: u32, create_parent: bool) -> Result<()> {
150        self.block_on(self.inner.mkdirs(path, permission, create_parent))
151    }
152
153    /// Rename `src` to `dst`.
154    pub fn rename(&self, src: &str, dst: &str, overwrite: bool) -> Result<()> {
155        self.block_on(self.inner.rename(src, dst, overwrite))
156    }
157
158    /// Delete the file or directory at `path`.
159    pub fn delete(&self, path: &str, recursive: bool) -> Result<bool> {
160        self.block_on(self.inner.delete(path, recursive))
161    }
162
163    /// Move a file or directory at `path` into the user's trash.
164    pub fn trash(&self, path: &str) -> Result<Option<String>> {
165        self.block_on(self.inner.trash(path))
166    }
167
168    /// Set modified and access times for a file.
169    pub fn set_times(&self, path: &str, mtime: u64, atime: u64) -> Result<()> {
170        self.block_on(self.inner.set_times(path, mtime, atime))
171    }
172
173    /// Optionally set the owner and group for a file.
174    pub fn set_owner(&self, path: &str, owner: Option<&str>, group: Option<&str>) -> Result<()> {
175        self.block_on(self.inner.set_owner(path, owner, group))
176    }
177
178    /// Set permissions for a file.
179    pub fn set_permission(&self, path: &str, permission: u32) -> Result<()> {
180        self.block_on(self.inner.set_permission(path, permission))
181    }
182
183    /// Set replication for a file.
184    pub fn set_replication(&self, path: &str, replication: u32) -> Result<bool> {
185        self.block_on(self.inner.set_replication(path, replication))
186    }
187
188    /// Get a content summary for a file or directory rooted at `path`.
189    pub fn get_content_summary(&self, path: &str) -> Result<ContentSummary> {
190        self.block_on(self.inner.get_content_summary(path))
191    }
192
193    /// Update ACL entries for file or directory at `path`.
194    pub fn modify_acl_entries(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
195        self.block_on(self.inner.modify_acl_entries(path, acl_spec))
196    }
197
198    /// Remove specific ACL entries for file or directory at `path`.
199    pub fn remove_acl_entries(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
200        self.block_on(self.inner.remove_acl_entries(path, acl_spec))
201    }
202
203    /// Remove all default ACL entries for file or directory at `path`.
204    pub fn remove_default_acl(&self, path: &str) -> Result<()> {
205        self.block_on(self.inner.remove_default_acl(path))
206    }
207
208    /// Remove all ACL entries for file or directory at `path`.
209    pub fn remove_acl(&self, path: &str) -> Result<()> {
210        self.block_on(self.inner.remove_acl(path))
211    }
212
213    /// Override ACL entries for file or directory at `path`.
214    pub fn set_acl(&self, path: &str, acl_spec: Vec<AclEntry>) -> Result<()> {
215        self.block_on(self.inner.set_acl(path, acl_spec))
216    }
217
218    /// Get ACL status for the file or directory at `path`.
219    pub fn get_acl_status(&self, path: &str) -> Result<AclStatus> {
220        self.block_on(self.inner.get_acl_status(path))
221    }
222
223    /// Get all file statuses matching the glob `pattern`.
224    pub fn glob_status(&self, pattern: &str) -> Result<Vec<FileStatus>> {
225        self.block_on(self.inner.glob_status(pattern))
226    }
227}
228
229impl Default for Client {
230    fn default() -> Self {
231        ClientBuilder::new()
232            .build()
233            .expect("Failed to create default client")
234    }
235}
236
237/// A blocking file status iterator.
238pub struct ListStatusIterator {
239    inner: client::ListStatusIterator,
240    rt: Arc<Runtime>,
241}
242
243impl Iterator for ListStatusIterator {
244    type Item = Result<FileStatus>;
245
246    fn next(&mut self) -> Option<Self::Item> {
247        self.rt.block_on(self.inner.next())
248    }
249}
250
251/// A blocking file reader.
252pub struct FileReader {
253    inner: AsyncFileReader,
254    rt: Arc<Runtime>,
255}
256
257impl FileReader {
258    /// Returns the total size of the file.
259    pub fn file_length(&self) -> usize {
260        self.inner.file_length()
261    }
262
263    /// Returns the remaining bytes left based on the current cursor position.
264    pub fn remaining(&self) -> usize {
265        self.inner.remaining()
266    }
267
268    /// Sets the cursor position.
269    pub fn set_position(&mut self, pos: usize) {
270        self.inner.set_position(pos);
271    }
272
273    /// Returns the current cursor position in the file.
274    pub fn tell(&self) -> usize {
275        self.inner.tell()
276    }
277
278    /// Read up to `len` bytes, advancing the internal position.
279    pub fn read_bytes(&mut self, len: usize) -> Result<Bytes> {
280        self.rt.block_on(self.inner.read_bytes(len))
281    }
282
283    /// Read up to `buf.len()` bytes into the provided slice.
284    pub fn read_into(&mut self, buf: &mut [u8]) -> Result<usize> {
285        self.rt.block_on(self.inner.read_into(buf))
286    }
287
288    /// Read up to `len` bytes starting at `offset`.
289    pub fn read_range(&self, offset: usize, len: usize) -> Result<Bytes> {
290        self.rt.block_on(self.inner.read_range(offset, len))
291    }
292
293    /// Read file data into an existing buffer.
294    pub fn read_range_buf(&self, buf: &mut [u8], offset: usize) -> Result<()> {
295        self.rt.block_on(self.inner.read_range_buf(buf, offset))
296    }
297
298    /// Return a blocking stream of `Bytes` objects containing the file content.
299    pub fn read_range_stream(&self, offset: usize, len: usize) -> FileReadStream {
300        FileReadStream {
301            inner: Mutex::new(self.inner.read_range_stream(offset, len).boxed()),
302            rt: Arc::clone(&self.rt),
303        }
304    }
305}
306
307impl Read for FileReader {
308    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
309        self.read_into(buf).map_err(io_error)
310    }
311}
312
313impl Seek for FileReader {
314    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
315        let file_length = self.file_length() as i128;
316        let current = self.tell() as i128;
317        let new_pos = match pos {
318            SeekFrom::Start(pos) => i128::from(pos),
319            SeekFrom::End(offset) => file_length + i128::from(offset),
320            SeekFrom::Current(offset) => current + i128::from(offset),
321        };
322
323        if new_pos < 0 || new_pos > file_length {
324            return Err(io::Error::new(
325                io::ErrorKind::InvalidInput,
326                "cannot seek outside of file bounds",
327            ));
328        }
329
330        self.inner.set_position(new_pos as usize);
331        Ok(new_pos as u64)
332    }
333}
334
335/// A blocking stream of file bytes.
336pub struct FileReadStream {
337    inner: Mutex<BoxStream<'static, Result<Bytes>>>,
338    rt: Arc<Runtime>,
339}
340
341impl Iterator for FileReadStream {
342    type Item = Result<Bytes>;
343
344    fn next(&mut self) -> Option<Self::Item> {
345        self.rt.block_on(self.inner.lock().unwrap().next())
346    }
347}
348
349/// A blocking file writer.
350pub struct FileWriter {
351    inner: AsyncFileWriter,
352    rt: Arc<Runtime>,
353}
354
355impl FileWriter {
356    /// Write bytes to the file.
357    pub fn write_bytes(&mut self, buf: Bytes) -> Result<usize> {
358        self.rt.block_on(self.inner.write_bytes(buf))
359    }
360
361    /// Close the file writer.
362    pub fn close(&mut self) -> Result<()> {
363        self.rt.block_on(self.inner.close())
364    }
365}
366
367impl Write for FileWriter {
368    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
369        self.write_bytes(Bytes::copy_from_slice(buf))
370            .map_err(io_error)
371    }
372
373    fn flush(&mut self) -> io::Result<()> {
374        Ok(())
375    }
376}