This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 376017634 feat(bindings/cpp): expose lister (#3011)
376017634 is described below

commit 3760176346e2f14f99f34e530f15f52410d54f5f
Author: Mingzhuo Yin <[email protected]>
AuthorDate: Wed Sep 6 10:57:56 2023 +0800

    feat(bindings/cpp): expose lister (#3011)
    
    Signed-off-by: silver-ymz <[email protected]>
---
 bindings/cpp/include/opendal.hpp  | 78 +++++++++++++++++++++++++++++++++++++++
 bindings/cpp/src/lib.rs           | 15 ++++++++
 bindings/cpp/src/lister.rs        | 31 ++++++++++++++++
 bindings/cpp/src/opendal.cpp      | 15 ++++++++
 bindings/cpp/src/types.rs         | 17 +++++++++
 bindings/cpp/tests/basic_test.cpp | 20 ++++++++++
 6 files changed, 176 insertions(+)

diff --git a/bindings/cpp/include/opendal.hpp b/bindings/cpp/include/opendal.hpp
index 80facdc5b..1440e84b6 100644
--- a/bindings/cpp/include/opendal.hpp
+++ b/bindings/cpp/include/opendal.hpp
@@ -69,6 +69,7 @@ struct Entry {
 };
 
 class Reader;
+class Lister;
 
 /**
  * @class Operator
@@ -184,6 +185,8 @@ public:
    */
   std::vector<Entry> list(std::string_view path);
 
+  Lister lister(std::string_view path);
+
 private:
   std::optional<rust::Box<opendal::ffi::Operator>> operator_;
 };
@@ -219,6 +222,7 @@ private:
  * @class ReaderStream
  * @brief ReaderStream is a stream wrapper of Reader which can provide
  * `iostream` interface.
+ * @note It's an undefined behavior to make multiple streams from one reader.
  */
 class ReaderStream
     : public boost::iostreams::stream<boost::reference_wrapper<Reader>> {
@@ -227,4 +231,78 @@ public:
       : boost::iostreams::stream<boost::reference_wrapper<Reader>>(
             boost::ref(reader)) {}
 };
+
+/**
+ * @class Lister
+ * @brief Lister is designed to list the entries of a directory.
+ * @details It provides next operation to get the next entry. You can also use
+ * it like an iterator.
+ * @code{.cpp}
+ * auto lister = operator.lister("dir/");
+ * for (const auto &entry : lister) {
+ *   // Do something with entry
+ * }
+ * @endcode
+ */
+class Lister {
+public:
+  Lister(rust::Box<opendal::ffi::Lister> &&lister)
+      : raw_lister_(std::move(lister)) {}
+
+  /**
+   * @class ListerIterator
+   * @brief ListerIterator is an iterator of Lister.
+   * @note It's an undefined behavior to make multiple iterators from one
+   * Lister.
+   */
+  class ListerIterator {
+  public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = Entry;
+    using difference_type = std::ptrdiff_t;
+    using pointer = Entry *;
+    using reference = Entry &;
+
+    ListerIterator(Lister &lister) : lister_(lister) {
+      current_entry_ = lister_.next();
+    }
+
+    Entry operator*() { return current_entry_.value(); }
+
+    ListerIterator &operator++() {
+      if (current_entry_) {
+        current_entry_ = lister_.next();
+      }
+      return *this;
+    }
+
+    bool operator!=(const ListerIterator &other) const {
+      return current_entry_ != std::nullopt ||
+             other.current_entry_ != std::nullopt;
+    }
+
+  protected:
+    // Only used for end iterator
+    ListerIterator(Lister &lister, bool /*end*/) : lister_(lister) {}
+
+  private:
+    Lister &lister_;
+    std::optional<Entry> current_entry_;
+
+    friend class Lister;
+  };
+
+  /**
+   * @brief Get the next entry of the lister
+   *
+   * @return The next entry of the lister
+   */
+  std::optional<Entry> next();
+
+  ListerIterator begin() { return ListerIterator(*this); }
+  ListerIterator end() { return ListerIterator(*this, true); }
+
+private:
+  rust::Box<opendal::ffi::Lister> raw_lister_;
+};
 } // namespace opendal
\ No newline at end of file
diff --git a/bindings/cpp/src/lib.rs b/bindings/cpp/src/lib.rs
index 8f4bc850b..4928719f0 100644
--- a/bindings/cpp/src/lib.rs
+++ b/bindings/cpp/src/lib.rs
@@ -15,10 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
+mod lister;
 mod reader;
 mod types;
 
 use anyhow::Result;
+use lister::Lister;
 use opendal as od;
 use reader::Reader;
 use std::str::FromStr;
@@ -48,6 +50,11 @@ mod ffi {
         value: String,
     }
 
+    struct OptionalEntry {
+        has_value: bool,
+        value: Entry,
+    }
+
     struct Metadata {
         mode: EntryMode,
         content_length: u64,
@@ -66,6 +73,7 @@ mod ffi {
     extern "Rust" {
         type Operator;
         type Reader;
+        type Lister;
 
         fn new_operator(scheme: &str, configs: Vec<HashMapValue>) -> 
Result<Box<Operator>>;
         fn read(self: &Operator, path: &str) -> Result<Vec<u8>>;
@@ -78,9 +86,12 @@ mod ffi {
         fn stat(self: &Operator, path: &str) -> Result<Metadata>;
         fn list(self: &Operator, path: &str) -> Result<Vec<Entry>>;
         fn reader(self: &Operator, path: &str) -> Result<Box<Reader>>;
+        fn lister(self: &Operator, path: &str) -> Result<Box<Lister>>;
 
         fn read(self: &mut Reader, buf: &mut [u8]) -> Result<usize>;
         fn seek(self: &mut Reader, offset: u64, dir: SeekFrom) -> Result<u64>;
+
+        fn next(self: &mut Lister) -> Result<OptionalEntry>;
     }
 }
 
@@ -144,4 +155,8 @@ impl Operator {
     fn reader(&self, path: &str) -> Result<Box<Reader>> {
         Ok(Box::new(Reader(self.0.reader(path)?)))
     }
+
+    fn lister(&self, path: &str) -> Result<Box<Lister>> {
+        Ok(Box::new(Lister(self.0.lister(path)?)))
+    }
 }
diff --git a/bindings/cpp/src/lister.rs b/bindings/cpp/src/lister.rs
new file mode 100644
index 000000000..573e7a669
--- /dev/null
+++ b/bindings/cpp/src/lister.rs
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use super::ffi;
+use anyhow::Result;
+use opendal as od;
+
+pub struct Lister(pub od::BlockingLister);
+
+impl Lister {
+    pub fn next(&mut self) -> Result<ffi::OptionalEntry> {
+        match self.0.next() {
+            Some(entry) => Ok(Some(entry?).into()),
+            None => Ok(None.into()),
+        }
+    }
+}
diff --git a/bindings/cpp/src/opendal.cpp b/bindings/cpp/src/opendal.cpp
index e8ffa75a8..a81af91cd 100644
--- a/bindings/cpp/src/opendal.cpp
+++ b/bindings/cpp/src/opendal.cpp
@@ -87,6 +87,10 @@ std::vector<Entry> Operator::list(std::string_view path) {
   return entries;
 }
 
+Lister Operator::lister(std::string_view path) {
+  return operator_.value()->lister(RUST_STR(path));
+}
+
 Reader Operator::reader(std::string_view path) {
   return operator_.value()->reader(RUST_STR(path));
 }
@@ -105,6 +109,17 @@ std::streampos Reader::seek(std::streamoff off, 
std::ios_base::seekdir dir) {
   return raw_reader_->seek(off, to_rust_seek_dir(dir));
 }
 
+// Lister
+
+std::optional<Entry> Lister::next() {
+  auto entry = raw_lister_->next();
+  if (entry.has_value) {
+    return std::move(entry.value);
+  } else {
+    return std::nullopt;
+  }
+}
+
 // Metadata
 
 std::optional<std::string> parse_optional_string(ffi::OptionalString &&s);
diff --git a/bindings/cpp/src/types.rs b/bindings/cpp/src/types.rs
index ccd456716..044c7f385 100644
--- a/bindings/cpp/src/types.rs
+++ b/bindings/cpp/src/types.rs
@@ -76,3 +76,20 @@ impl From<Option<String>> for ffi::OptionalString {
         }
     }
 }
+
+impl From<Option<od::Entry>> for ffi::OptionalEntry {
+    fn from(entry: Option<od::Entry>) -> Self {
+        match entry {
+            Some(entry) => Self {
+                has_value: true,
+                value: entry.into(),
+            },
+            None => Self {
+                has_value: false,
+                value: ffi::Entry {
+                    path: String::default(),
+                },
+            },
+        }
+    }
+}
diff --git a/bindings/cpp/tests/basic_test.cpp 
b/bindings/cpp/tests/basic_test.cpp
index c6c47c001..096164ec3 100644
--- a/bindings/cpp/tests/basic_test.cpp
+++ b/bindings/cpp/tests/basic_test.cpp
@@ -139,6 +139,26 @@ TEST_F(OpendalTest, ReaderTest) {
   EXPECT_EQ(reader_data, data);
 }
 
+TEST_F(OpendalTest, ListerTest) {
+  op.create_dir("test_dir/");
+  op.write("test_dir/test1", {1, 2, 3});
+  op.write("test_dir/test2", {4, 5, 6});
+
+  int size = 0;
+  auto lister = op.lister("test_dir/");
+  for (const auto &entry : lister) {
+    EXPECT_TRUE(entry.path.find("test_dir/test") == 0);
+    size += 1;
+  }
+  EXPECT_EQ(size, 2);
+
+  lister = op.lister("test_dir/");
+  std::vector<opendal::Entry> paths(lister.begin(), lister.end());
+  EXPECT_EQ(paths.size(), 2);
+  EXPECT_EQ(paths[0].path, "test_dir/test1");
+  EXPECT_EQ(paths[1].path, "test_dir/test2");
+}
+
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();

Reply via email to