This is an automated email from the ASF dual-hosted git repository. silver pushed a commit to branch cpp-more-operation in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
commit eb3d82e712e22146f9e334961f9a40b165de5617 Author: silver-ymz <[email protected]> AuthorDate: Tue Sep 5 21:10:36 2023 +0800 feat(bindings/cpp): expose lister 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();
