opendal_core/blocking/
operator.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::time::Duration;
19
20use tokio::runtime::Handle;
21
22use crate::Operator as AsyncOperator;
23use crate::raw::PresignedRequest;
24use crate::types::IntoOperatorUri;
25use crate::*;
26
27/// Use OpenDAL in blocking context.
28///
29/// # Notes
30///
31/// blocking::Operator is a wrapper around [`AsyncOperator`]. It calls async runtimes' `block_on` API to spawn blocking tasks.
32/// Please avoid using blocking::Operator in async context.
33///
34/// # Examples
35///
36/// ## Init in async context
37///
38/// blocking::Operator will use current async context's runtime to handle the async calls.
39///
40/// This is just for initialization. You must use `blocking::Operator` in blocking context.
41///
42/// ```rust,no_run
43/// # use opendal_core::services;
44/// # use opendal_core::blocking;
45/// # use opendal_core::Operator;
46/// # use opendal_core::Result;
47///
48/// #[tokio::main]
49/// async fn main() -> Result<()> {
50///     // Create fs backend builder.
51///     let builder = services::Memory::default();
52///     let op = Operator::new(builder)?.finish();
53///
54///     // Build an `blocking::Operator` with blocking layer to start operating the storage.
55///     let _: blocking::Operator = blocking::Operator::new(op)?;
56///
57///     Ok(())
58/// }
59/// ```
60///
61/// ## In async context with blocking functions
62///
63/// If `blocking::Operator` is called in blocking function, please fetch a [`tokio::runtime::EnterGuard`]
64/// first. You can use [`Handle::try_current`] first to get the handle and then call [`Handle::enter`].
65/// This often happens in the case that async function calls blocking function.
66///
67/// ```rust,no_run
68/// # use opendal_core::services;
69/// # use opendal_core::blocking;
70/// # use opendal_core::Operator;
71/// # use opendal_core::Result;
72///
73/// #[tokio::main]
74/// async fn main() -> Result<()> {
75///     let _ = blocking_fn()?;
76///     Ok(())
77/// }
78///
79/// fn blocking_fn() -> Result<blocking::Operator> {
80///     // Create fs backend builder.
81///     let builder = services::Memory::default();
82///     let op = Operator::new(builder)?.finish();
83///
84///     let handle = tokio::runtime::Handle::try_current().unwrap();
85///     let _guard = handle.enter();
86///     // Build an `blocking::Operator` to start operating the storage.
87///     let op: blocking::Operator = blocking::Operator::new(op)?;
88///     Ok(op)
89/// }
90/// ```
91///
92/// ## In blocking context
93///
94/// In a pure blocking context, we can create a runtime and use it to create the `blocking::Operator`.
95///
96/// > The following code uses a global statically created runtime as an example, please manage the
97/// > runtime on demand.
98///
99/// ```rust,no_run
100/// # use std::sync::LazyLock;
101/// # use opendal_core::services;
102/// # use opendal_core::blocking;
103/// # use opendal_core::Operator;
104/// # use opendal_core::Result;
105///
106/// static RUNTIME: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
107///     tokio::runtime::Builder::new_multi_thread()
108///         .enable_all()
109///         .build()
110///         .unwrap()
111/// });
112///
113/// fn main() -> Result<()> {
114///     // Create fs backend builder.
115///     let builder = services::Memory::default();
116///     let op = Operator::new(builder)?.finish();
117///
118///     // Fetch the `EnterGuard` from global runtime.
119///     let _guard = RUNTIME.enter();
120///     // Build an `blocking::Operator` with blocking layer to start operating the storage.
121///     let _: blocking::Operator = blocking::Operator::new(op)?;
122///
123///     Ok(())
124/// }
125/// ```
126#[derive(Clone, Debug)]
127pub struct Operator {
128    handle: tokio::runtime::Handle,
129    op: AsyncOperator,
130}
131
132impl Operator {
133    /// Create a new `BlockingLayer` with the current runtime's handle
134    pub fn new(op: AsyncOperator) -> Result<Self> {
135        Ok(Self {
136            handle: Handle::try_current()
137                .map_err(|_| Error::new(ErrorKind::Unexpected, "failed to get current handle"))?,
138            op,
139        })
140    }
141
142    /// Spawn a future onto the runtime's worker pool and block until it
143    /// completes.
144    ///
145    /// Unlike [`Handle::block_on`] which polls the future on the **calling**
146    /// thread's stack, this method runs the future on a tokio worker thread
147    /// (typically 8 MB stack) and only uses the calling thread to wait for
148    /// the result.  This avoids stack overflows when the async state machine
149    /// is deeply nested (e.g. HF/XET uploads driven from a JVM thread with
150    /// a 1 MB default stack).
151    fn spawn_block<F>(&self, f: F) -> Result<F::Output>
152    where
153        F: std::future::Future + Send + 'static,
154        F::Output: Send + 'static,
155    {
156        self.handle.block_on(self.handle.spawn(f)).map_err(|err| {
157            Error::new(ErrorKind::Unexpected, "blocking task failed").set_source(err)
158        })
159    }
160
161    /// Create a blocking operator from URI based configuration.
162    pub fn from_uri(uri: impl IntoOperatorUri) -> Result<Self> {
163        let op = AsyncOperator::from_uri(uri)?;
164        Self::new(op)
165    }
166
167    /// Get information of underlying accessor.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// # use std::sync::Arc;
173    /// use opendal_core::blocking;
174    /// # use anyhow::Result;
175    /// use opendal_core::blocking::Operator;
176    ///
177    /// # fn test(op: blocking::Operator) -> Result<()> {
178    /// let info = op.info();
179    /// # Ok(())
180    /// # }
181    /// ```
182    pub fn info(&self) -> OperatorInfo {
183        self.op.info()
184    }
185}
186
187/// # Operator blocking API.
188impl Operator {
189    /// Create a presigned request for stat.
190    ///
191    /// See [`Operator::presign_stat`] for more details.
192    pub fn presign_stat(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
193        self.handle.block_on(self.op.presign_stat(path, expire))
194    }
195
196    /// Create a presigned request for read.
197    ///
198    /// See [`Operator::presign_read`] for more details.
199    pub fn presign_read(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
200        self.handle.block_on(self.op.presign_read(path, expire))
201    }
202
203    /// Create a presigned request for write.
204    ///
205    /// See [`Operator::presign_write`] for more details.
206    pub fn presign_write(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
207        self.handle.block_on(self.op.presign_write(path, expire))
208    }
209
210    /// Create a presigned request for delete.
211    ///
212    /// See [`Operator::presign_delete`] for more details.
213    pub fn presign_delete(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
214        self.handle.block_on(self.op.presign_delete(path, expire))
215    }
216
217    /// Get given path's metadata.
218    ///
219    /// # Behavior
220    ///
221    /// ## Services that support `create_dir`
222    ///
223    /// `test` and `test/` may vary in some services such as S3. However, on a local file system,
224    /// they're identical. Therefore, the behavior of `stat("test")` and `stat("test/")` might differ
225    /// in certain edge cases. Always use `stat("test/")` when you need to access a directory if possible.
226    ///
227    /// Here are the behavior list:
228    ///
229    /// | Case                   | Path            | Result                                     |
230    /// |------------------------|-----------------|--------------------------------------------|
231    /// | stat existing dir      | `abc/`          | Metadata with dir mode                     |
232    /// | stat existing file     | `abc/def_file`  | Metadata with file mode                    |
233    /// | stat dir without `/`   | `abc/def_dir`   | Error `NotFound` or metadata with dir mode |
234    /// | stat file with `/`     | `abc/def_file/` | Error `NotFound`                           |
235    /// | stat not existing path | `xyz`           | Error `NotFound`                           |
236    ///
237    /// Refer to [RFC: List Prefix][crate::docs::rfcs::rfc_3243_list_prefix] for more details.
238    ///
239    /// ## Services that not support `create_dir`
240    ///
241    /// For services that not support `create_dir`, `stat("test/")` will return `NotFound` even
242    /// when `test/abc` exists since the service won't have the concept of dir. There is nothing
243    /// we can do about this.
244    ///
245    /// # Examples
246    ///
247    /// ## Check if file exists
248    ///
249    /// ```
250    /// # use anyhow::Result;
251    /// # use futures::io;
252    /// use opendal_core::blocking;
253    /// # use opendal_core::blocking::Operator;
254    /// use opendal_core::ErrorKind;
255    /// #
256    /// # fn test(op: blocking::Operator) -> Result<()> {
257    /// if let Err(e) = op.stat("test") {
258    ///     if e.kind() == ErrorKind::NotFound {
259    ///         println!("file not exist")
260    ///     }
261    /// }
262    /// # Ok(())
263    /// # }
264    /// ```
265    pub fn stat(&self, path: &str) -> Result<Metadata> {
266        self.stat_options(path, options::StatOptions::default())
267    }
268
269    /// Get given path's metadata with extra options.
270    ///
271    /// # Behavior
272    ///
273    /// ## Services that support `create_dir`
274    ///
275    /// `test` and `test/` may vary in some services such as S3. However, on a local file system,
276    /// they're identical. Therefore, the behavior of `stat("test")` and `stat("test/")` might differ
277    /// in certain edge cases. Always use `stat("test/")` when you need to access a directory if possible.
278    ///
279    /// Here are the behavior list:
280    ///
281    /// | Case                   | Path            | Result                                     |
282    /// |------------------------|-----------------|--------------------------------------------|
283    /// | stat existing dir      | `abc/`          | Metadata with dir mode                     |
284    /// | stat existing file     | `abc/def_file`  | Metadata with file mode                    |
285    /// | stat dir without `/`   | `abc/def_dir`   | Error `NotFound` or metadata with dir mode |
286    /// | stat file with `/`     | `abc/def_file/` | Error `NotFound`                           |
287    /// | stat not existing path | `xyz`           | Error `NotFound`                           |
288    ///
289    /// Refer to [RFC: List Prefix][crate::docs::rfcs::rfc_3243_list_prefix] for more details.
290    ///
291    /// ## Services that not support `create_dir`
292    ///
293    /// For services that not support `create_dir`, `stat("test/")` will return `NotFound` even
294    /// when `test/abc` exists since the service won't have the concept of dir. There is nothing
295    /// we can do about this.
296    pub fn stat_options(&self, path: &str, opts: options::StatOptions) -> Result<Metadata> {
297        let op = self.op.clone();
298        let path = path.to_string();
299        self.spawn_block(async move { op.stat_options(&path, opts).await })?
300    }
301
302    /// Check if this path exists or not.
303    ///
304    /// # Example
305    ///
306    /// ```no_run
307    /// use anyhow::Result;
308    /// use opendal_core::blocking;
309    /// use opendal_core::blocking::Operator;
310    /// fn test(op: blocking::Operator) -> Result<()> {
311    ///     let _ = op.exists("test")?;
312    ///
313    ///     Ok(())
314    /// }
315    /// ```
316    pub fn exists(&self, path: &str) -> Result<bool> {
317        let r = self.stat(path);
318        match r {
319            Ok(_) => Ok(true),
320            Err(err) => match err.kind() {
321                ErrorKind::NotFound => Ok(false),
322                _ => Err(err),
323            },
324        }
325    }
326
327    /// Create a dir at given path.
328    ///
329    /// # Notes
330    ///
331    /// To indicate that a path is a directory, it is compulsory to include
332    /// a trailing / in the path. Failure to do so may result in
333    /// `NotADirectory` error being returned by OpenDAL.
334    ///
335    /// # Behavior
336    ///
337    /// - Create on existing dir will succeed.
338    /// - Create dir is always recursive, works like `mkdir -p`
339    ///
340    /// # Examples
341    ///
342    /// ```no_run
343    /// # use opendal_core::Result;
344    /// use opendal_core::blocking;
345    /// # use opendal_core::blocking::Operator;
346    /// # use futures::TryStreamExt;
347    /// # fn test(op: blocking::Operator) -> Result<()> {
348    /// op.create_dir("path/to/dir/")?;
349    /// # Ok(())
350    /// # }
351    /// ```
352    pub fn create_dir(&self, path: &str) -> Result<()> {
353        let op = self.op.clone();
354        let path = path.to_string();
355        self.spawn_block(async move { op.create_dir(&path).await })?
356    }
357
358    /// Read the whole path into a bytes.
359    ///
360    /// This function will allocate a new bytes internally. For more precise memory control or
361    /// reading data lazily, please use [`blocking::Operator::reader`]
362    ///
363    /// # Examples
364    ///
365    /// ```no_run
366    /// # use opendal_core::Result;
367    /// use opendal_core::blocking;
368    /// # use opendal_core::blocking::Operator;
369    /// #
370    /// # fn test(op: blocking::Operator) -> Result<()> {
371    /// let bs = op.read("path/to/file")?;
372    /// # Ok(())
373    /// # }
374    /// ```
375    pub fn read(&self, path: &str) -> Result<Buffer> {
376        self.read_options(path, options::ReadOptions::default())
377    }
378
379    /// Read the whole path into a bytes with extra options.
380    ///
381    /// This function will allocate a new bytes internally. For more precise memory control or
382    /// reading data lazily, please use [`blocking::Operator::reader`]
383    pub fn read_options(&self, path: &str, opts: options::ReadOptions) -> Result<Buffer> {
384        let op = self.op.clone();
385        let path = path.to_string();
386        self.spawn_block(async move { op.read_options(&path, opts).await })?
387    }
388
389    /// Create a new reader which can read the whole path.
390    ///
391    /// # Examples
392    ///
393    /// ```no_run
394    /// # use opendal_core::Result;
395    /// use opendal_core::blocking;
396    /// # use opendal_core::blocking::Operator;
397    /// # use futures::TryStreamExt;
398    /// # fn test(op: blocking::Operator) -> Result<()> {
399    /// let r = op.reader("path/to/file")?;
400    /// # Ok(())
401    /// # }
402    /// ```
403    pub fn reader(&self, path: &str) -> Result<blocking::Reader> {
404        self.reader_options(path, options::ReaderOptions::default())
405    }
406
407    /// Create a new reader with extra options
408    pub fn reader_options(
409        &self,
410        path: &str,
411        opts: options::ReaderOptions,
412    ) -> Result<blocking::Reader> {
413        let r = self.handle.block_on(self.op.reader_options(path, opts))?;
414        Ok(blocking::Reader::new(self.handle.clone(), r))
415    }
416
417    /// Write bytes into given path.
418    ///
419    /// # Notes
420    ///
421    /// - Write will make sure all bytes has been written, or an error will be returned.
422    ///
423    /// # Examples
424    ///
425    /// ```no_run
426    /// # use opendal_core::Result;
427    /// # use opendal_core::blocking::Operator;
428    /// # use futures::StreamExt;
429    /// # use futures::SinkExt;
430    /// use bytes::Bytes;
431    /// use opendal_core::blocking;
432    ///
433    /// # fn test(op: blocking::Operator) -> Result<()> {
434    /// op.write("path/to/file", vec![0; 4096])?;
435    /// # Ok(())
436    /// # }
437    /// ```
438    pub fn write(&self, path: &str, bs: impl Into<Buffer>) -> Result<Metadata> {
439        self.write_options(path, bs, options::WriteOptions::default())
440    }
441
442    /// Write data with options.
443    ///
444    /// # Notes
445    ///
446    /// - Write will make sure all bytes has been written, or an error will be returned.
447    pub fn write_options(
448        &self,
449        path: &str,
450        bs: impl Into<Buffer>,
451        opts: options::WriteOptions,
452    ) -> Result<Metadata> {
453        let op = self.op.clone();
454        let path = path.to_string();
455        let bs = bs.into();
456        self.spawn_block(async move { op.write_options(&path, bs, opts).await })?
457    }
458
459    /// Write multiple bytes into given path.
460    ///
461    /// # Notes
462    ///
463    /// - Write will make sure all bytes has been written, or an error will be returned.
464    ///
465    /// # Examples
466    ///
467    /// ```no_run
468    /// # use opendal_core::Result;
469    /// # use opendal_core::blocking;
470    /// # use opendal_core::blocking::Operator;
471    /// # use futures::StreamExt;
472    /// # use futures::SinkExt;
473    /// use bytes::Bytes;
474    ///
475    /// # fn test(op: blocking::Operator) -> Result<()> {
476    /// let mut w = op.writer("path/to/file")?;
477    /// w.write(vec![0; 4096])?;
478    /// w.write(vec![1; 4096])?;
479    /// w.close()?;
480    /// # Ok(())
481    /// # }
482    /// ```
483    pub fn writer(&self, path: &str) -> Result<blocking::Writer> {
484        self.writer_options(path, options::WriteOptions::default())
485    }
486
487    /// Create a new writer with extra options
488    pub fn writer_options(
489        &self,
490        path: &str,
491        opts: options::WriteOptions,
492    ) -> Result<blocking::Writer> {
493        let w = self.handle.block_on(self.op.writer_options(path, opts))?;
494        Ok(blocking::Writer::new(self.handle.clone(), w))
495    }
496
497    /// Copy a file from `from` to `to`.
498    ///
499    /// # Notes
500    ///
501    /// - `from` and `to` must be a file.
502    /// - `to` will be overwritten if it exists.
503    /// - If `from` and `to` are the same, nothing will happen.
504    /// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
505    ///
506    /// # Examples
507    ///
508    /// ```
509    /// # use opendal_core::Result;
510    /// use opendal_core::blocking;
511    /// # use opendal_core::blocking::Operator;
512    ///
513    /// # fn test(op: blocking::Operator) -> Result<()> {
514    /// op.copy("path/to/file", "path/to/file2")?;
515    /// # Ok(())
516    /// # }
517    /// ```
518    pub fn copy(&self, from: &str, to: &str) -> Result<Metadata> {
519        self.copy_options(from, to, options::CopyOptions::default())
520    }
521
522    /// Copy a file from `from` to `to` with additional options.
523    pub fn copy_options(
524        &self,
525        from: &str,
526        to: &str,
527        opts: options::CopyOptions,
528    ) -> Result<Metadata> {
529        let op = self.op.clone();
530        let from = from.to_string();
531        let to = to.to_string();
532        self.spawn_block(async move { op.copy_options(&from, &to, opts).await })?
533    }
534
535    /// Create a copier from `from` to `to`.
536    ///
537    /// This function creates a new [`blocking::Copier`] that implements
538    /// `Iterator<Item = Result<usize>>`.
539    pub fn copier(&self, from: &str, to: &str) -> Result<blocking::Copier> {
540        self.copier_options(from, to, options::CopyOptions::default())
541    }
542
543    /// Create a copier from `from` to `to` with additional options.
544    pub fn copier_options(
545        &self,
546        from: &str,
547        to: &str,
548        opts: options::CopyOptions,
549    ) -> Result<blocking::Copier> {
550        let copier = self
551            .handle
552            .block_on(self.op.copier_options(from, to, opts))?;
553        Ok(blocking::Copier::new(self.handle.clone(), copier))
554    }
555
556    /// Rename a file from `from` to `to`.
557    ///
558    /// # Notes
559    ///
560    /// - `from` and `to` must be a file.
561    /// - `to` will be overwritten if it exists.
562    /// - If `from` and `to` are the same, a `IsSameFile` error will occur.
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// # use opendal_core::Result;
568    /// use opendal_core::blocking;
569    /// # use opendal_core::blocking::Operator;
570    ///
571    /// # fn test(op: blocking::Operator) -> Result<()> {
572    /// op.rename("path/to/file", "path/to/file2")?;
573    /// # Ok(())
574    /// # }
575    /// ```
576    pub fn rename(&self, from: &str, to: &str) -> Result<()> {
577        let op = self.op.clone();
578        let from = from.to_string();
579        let to = to.to_string();
580        self.spawn_block(async move { op.rename(&from, &to).await })?
581    }
582
583    /// Delete given path.
584    ///
585    /// # Notes
586    ///
587    /// - Delete not existing error won't return errors.
588    ///
589    /// # Examples
590    ///
591    /// ```no_run
592    /// # use anyhow::Result;
593    /// # use futures::io;
594    /// use opendal_core::blocking;
595    /// # use opendal_core::blocking::Operator;
596    /// # fn test(op: blocking::Operator) -> Result<()> {
597    /// op.delete("path/to/file")?;
598    /// # Ok(())
599    /// # }
600    /// ```
601    pub fn delete(&self, path: &str) -> Result<()> {
602        self.delete_options(path, options::DeleteOptions::default())
603    }
604
605    /// Delete given path with options.
606    ///
607    /// # Notes
608    ///
609    /// - Delete not existing error won't return errors.
610    pub fn delete_options(&self, path: &str, opts: options::DeleteOptions) -> Result<()> {
611        let op = self.op.clone();
612        let path = path.to_string();
613        self.spawn_block(async move { op.delete_options(&path, opts).await })?
614    }
615
616    /// Delete an infallible iterator of paths.
617    ///
618    /// Also see:
619    ///
620    /// - [`blocking::Operator::delete_try_iter`]: delete an fallible iterator of paths.
621    pub fn delete_iter<I, D>(&self, iter: I) -> Result<()>
622    where
623        I: IntoIterator<Item = D>,
624        D: IntoDeleteInput,
625    {
626        self.handle.block_on(self.op.delete_iter(iter))
627    }
628
629    /// Delete a fallible iterator of paths.
630    ///
631    /// Also see:
632    ///
633    /// - [`blocking::Operator::delete_iter`]: delete an infallible iterator of paths.
634    pub fn delete_try_iter<I, D>(&self, try_iter: I) -> Result<()>
635    where
636        I: IntoIterator<Item = Result<D>>,
637        D: IntoDeleteInput,
638    {
639        self.handle.block_on(self.op.delete_try_iter(try_iter))
640    }
641
642    /// Create a [`BlockingDeleter`] to continuously remove content from storage.
643    ///
644    /// It leverages batch deletion capabilities provided by storage services for efficient removal.
645    ///
646    /// Users can have more control over the deletion process by using [`BlockingDeleter`] directly.
647    pub fn deleter(&self) -> Result<blocking::Deleter> {
648        blocking::Deleter::create(
649            self.handle.clone(),
650            self.handle.block_on(self.op.deleter())?,
651        )
652    }
653
654    /// Remove the path and all nested dirs and files recursively.
655    ///
656    /// # Deprecated
657    ///
658    /// This method is deprecated since v0.55.0. Use [`blocking::Operator::delete_options`] with
659    /// `recursive: true` instead.
660    ///
661    /// ## Migration Example
662    ///
663    /// Instead of:
664    /// ```ignore
665    /// op.remove_all("path/to/dir")?;
666    /// ```
667    ///
668    /// Use:
669    /// ```ignore
670    /// use opendal_core::options::DeleteOptions;
671    /// op.delete_options("path/to/dir", DeleteOptions {
672    ///     recursive: true,
673    ///     ..Default::default()
674    /// })?;
675    /// ```
676    ///
677    /// # Notes
678    ///
679    /// If underlying services support delete in batch, we will use batch
680    /// delete instead.
681    ///
682    /// # Examples
683    ///
684    /// ```
685    /// # use anyhow::Result;
686    /// # use futures::io;
687    /// use opendal_core::blocking;
688    /// # use opendal_core::blocking::Operator;
689    /// # fn test(op: blocking::Operator) -> Result<()> {
690    /// op.remove_all("path/to/dir")?;
691    /// # Ok(())
692    /// # }
693    /// ```
694    #[deprecated(
695        since = "0.55.0",
696        note = "Use `delete_options` with `recursive: true` instead"
697    )]
698    #[allow(deprecated)]
699    pub fn remove_all(&self, path: &str) -> Result<()> {
700        self.delete_options(
701            path,
702            options::DeleteOptions {
703                recursive: true,
704                ..Default::default()
705            },
706        )
707    }
708
709    /// List entries whose paths start with the given prefix `path`.
710    ///
711    /// # Semantics
712    ///
713    /// - Listing is **prefix-based**; it doesn't require the parent directory to exist.
714    /// - If `path` itself exists, it is returned as an entry along with prefixed children.
715    /// - If `path` is missing but deeper objects exist, the list succeeds and returns those prefixed entries instead of an error.
716    /// - Set `recursive` in [`options::ListOptions`] via [`list_options`](Self::list_options) to walk all descendants; the default returns only immediate children when delimiter is supported.
717    ///
718    /// ## Streaming List
719    ///
720    /// This function materializes the full result in memory. For large listings, prefer [`blocking::Operator::lister`] to stream entries.
721    ///
722    /// # Examples
723    ///
724    /// ```no_run
725    /// # use anyhow::Result;
726    /// use opendal_core::blocking;
727    /// use opendal_core::blocking::Operator;
728    /// use opendal_core::EntryMode;
729    /// #  fn test(op: blocking::Operator) -> Result<()> {
730    /// let mut entries = op.list("path/to/dir/")?;
731    /// for entry in entries {
732    ///     match entry.metadata().mode() {
733    ///         EntryMode::FILE => {
734    ///             println!("Handling file")
735    ///         }
736    ///         EntryMode::DIR => {
737    ///             println!("Handling dir {}", entry.path())
738    ///         }
739    ///         EntryMode::Unknown => continue,
740    ///     }
741    /// }
742    /// # Ok(())
743    /// # }
744    /// ```
745    pub fn list(&self, path: &str) -> Result<Vec<Entry>> {
746        self.list_options(path, options::ListOptions::default())
747    }
748
749    /// List entries whose paths start with the given prefix `path` with additional options.
750    ///
751    /// # Semantics
752    ///
753    /// Inherits the prefix semantics described in [`Operator::list`] (blocking variant). It returns `path` itself if present and tolerates missing parents when prefixed objects exist.
754    ///
755    /// ## Streaming List
756    ///
757    /// This function materializes the full result in memory. For large listings, prefer [`blocking::Operator::lister`] to stream entries.
758    ///
759    /// ## Options
760    ///
761    /// See [`options::ListOptions`] for the full set. Common knobs: traversal (`recursive`), pagination (`limit`, `start_after`), and versioning (`versions`, `deleted`).
762    pub fn list_options(&self, path: &str, opts: options::ListOptions) -> Result<Vec<Entry>> {
763        let op = self.op.clone();
764        let path = path.to_string();
765        self.spawn_block(async move { op.list_options(&path, opts).await })?
766    }
767
768    /// Create a streaming lister for entries whose paths start with the given prefix `path`.
769    ///
770    /// This function creates a new [`BlockingLister`]; dropping it stops listing.
771    ///
772    /// # Semantics
773    ///
774    /// Shares the same prefix semantics as [`blocking::Operator::list`]: parent directory is optional; `path` itself is yielded if present; missing parents with deeper objects are accepted.
775    ///
776    /// ## Options
777    ///
778    /// Accepts the same [`options::ListOptions`] as [`list_options`](Self::list_options): traversal (`recursive`), pagination (`limit`, `start_after`), and versioning (`versions`, `deleted`).
779    ///
780    /// # Examples
781    ///
782    /// ```no_run
783    /// # use anyhow::Result;
784    /// # use futures::io;
785    /// use futures::TryStreamExt;
786    /// use opendal_core::blocking;
787    /// use opendal_core::blocking::Operator;
788    /// use opendal_core::EntryMode;
789    /// # fn test(op: blocking::Operator) -> Result<()> {
790    /// let mut ds = op.lister("path/to/dir/")?;
791    /// for de in ds {
792    ///     let de = de?;
793    ///     match de.metadata().mode() {
794    ///         EntryMode::FILE => {
795    ///             println!("Handling file")
796    ///         }
797    ///         EntryMode::DIR => {
798    ///             println!("Handling dir like start a new list via meta.path()")
799    ///         }
800    ///         EntryMode::Unknown => continue,
801    ///     }
802    /// }
803    /// # Ok(())
804    /// # }
805    /// ```
806    pub fn lister(&self, path: &str) -> Result<blocking::Lister> {
807        self.lister_options(path, options::ListOptions::default())
808    }
809
810    /// List entries under a prefix as an iterator with options.
811    ///
812    /// This function creates a new handle to stream entries and inherits the prefix semantics of [`blocking::Operator::list`].
813    ///
814    /// ## Options
815    ///
816    /// Same as [`lister`](Self::lister); see [`options::ListOptions`] for traversal, pagination, and versioning knobs.
817    pub fn lister_options(
818        &self,
819        path: &str,
820        opts: options::ListOptions,
821    ) -> Result<blocking::Lister> {
822        let l = self.handle.block_on(self.op.lister_options(path, opts))?;
823        Ok(blocking::Lister::new(self.handle.clone(), l))
824    }
825
826    /// Check if this operator can work correctly.
827    ///
828    /// We will send a `list` request to path and return any errors we met.
829    ///
830    /// ```
831    /// # use std::sync::Arc;
832    /// # use anyhow::Result;
833    /// use opendal_core::blocking;
834    /// use opendal_core::blocking::Operator;
835    /// use opendal_core::ErrorKind;
836    ///
837    /// # fn test(op: blocking::Operator) -> Result<()> {
838    /// op.check()?;
839    /// # Ok(())
840    /// # }
841    /// ```
842    pub fn check(&self) -> Result<()> {
843        let mut ds = self.lister("/")?;
844
845        match ds.next() {
846            Some(Err(e)) if e.kind() != ErrorKind::NotFound => Err(e),
847            _ => Ok(()),
848        }
849    }
850}
851
852impl From<Operator> for AsyncOperator {
853    fn from(val: Operator) -> Self {
854        val.op
855    }
856}