Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bindings/cpp): expose lister #3011

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions bindings/cpp/include/opendal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ struct Entry {
};

class Reader;
class Lister;

/**
* @class Operator
Expand Down Expand Up @@ -184,6 +185,8 @@ class Operator {
*/
std::vector<Entry> list(std::string_view path);

Lister lister(std::string_view path);

private:
std::optional<rust::Box<opendal::ffi::Operator>> operator_;
};
Expand Down Expand Up @@ -219,6 +222,7 @@ class Reader
* @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>> {
Expand All @@ -227,4 +231,78 @@ class ReaderStream
: 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
15 changes: 15 additions & 0 deletions bindings/cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +50,11 @@ mod ffi {
value: String,
}

struct OptionalEntry {
has_value: bool,
value: Entry,
}

struct Metadata {
mode: EntryMode,
content_length: u64,
Expand All @@ -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>>;
Expand All @@ -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>;
}
}

Expand Down Expand Up @@ -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)?)))
}
}
31 changes: 31 additions & 0 deletions bindings/cpp/src/lister.rs
Original file line number Diff line number Diff line change
@@ -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()),
}
}
}
15 changes: 15 additions & 0 deletions bindings/cpp/src/opendal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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);
Expand Down
17 changes: 17 additions & 0 deletions bindings/cpp/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
},
}
}
}
20 changes: 20 additions & 0 deletions bindings/cpp/tests/basic_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down