Page MenuHomePhorge

No OneTemporary

Size
24 KB
Referenced Files
None
Subscribers
None
diff --git a/src/base/asio-std-file-handler.hpp b/src/base/asio-std-file-handler.hpp
new file mode 100644
index 0000000..c9cdaaa
--- /dev/null
+++ b/src/base/asio-std-file-handler.hpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * This file is part of libkazv.
+ *
+ * libkazv is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * libkazv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with libkazv. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+#include "libkazv-config.hpp"
+
+#include <fstream>
+
+#include <boost/asio.hpp>
+
+#include "file-desc.hpp"
+
+namespace Kazv
+{
+ template<class Exec>
+ class AsioStdFileStream
+ {
+ public:
+ using DataT = FileContent;
+ /**
+ * Constructor.
+ *
+ * @param exec A boost::asio executor. It should run actions
+ * sequentially.
+ * @param filename The file name of this FileStream.
+ */
+ inline AsioStdFileStream(Exec exec, std::string filename, FileOpenMode mode)
+ : m_executor(exec)
+ , m_filename(filename)
+ , m_mode(mode)
+ , m_stream(std::make_shared<std::fstream>(
+ m_filename,
+ (m_mode == FileOpenMode::Read
+ ? std::ios_base::in
+ : std::ios_base::out)
+ | std::ios_base::binary))
+ {}
+
+ inline bool valid() { return !!m_stream; }
+
+ template<class Callback>
+ void read(int maxSize, Callback readCallback) {
+ boost::asio::post(
+ m_executor,
+ [stream=m_stream, maxSize, readCallback=std::move(readCallback)]() {
+ auto buf = std::vector<char>(maxSize, '\0');
+ auto data = FileContent{};
+ auto actualSize = int{};
+
+ try {
+ stream->read(buf.data(), maxSize);
+
+ actualSize = stream->gcount();
+
+ data = FileContent(buf.begin(), buf.begin() + actualSize);
+ } catch (const std::exception &) {
+ readCallback(FileOpRetCode::Error, FileContent{});
+ return;
+ }
+
+ if (actualSize > 0) {
+ readCallback(FileOpRetCode::Success, data);
+ } else if (stream->eof()) {
+ readCallback(FileOpRetCode::Eof, FileContent{});
+ } else {
+ readCallback(FileOpRetCode::Error, FileContent{});
+ }
+ });
+ }
+
+ template<class Callback>
+ void write(DataT data, Callback writeCallback) {
+ boost::asio::post(
+ m_executor,
+ [stream=m_stream, data, writeCallback=std::move(writeCallback)]() {
+ try {
+ auto buf = std::vector<char>(data.begin(), data.end());
+ stream->write(buf.data(), buf.size());
+ } catch (const std::exception &) {
+ writeCallback(FileOpRetCode::Error, 0);
+ return;
+ }
+
+ if (stream->bad()) {
+ writeCallback(FileOpRetCode::Error, 0);
+ } else {
+ writeCallback(FileOpRetCode::Success, data.size());
+ }
+ });
+ }
+
+ private:
+ Exec m_executor;
+ std::string m_filename;
+ FileOpenMode m_mode;
+ std::shared_ptr<std::fstream> m_stream;
+ };
+
+ template<class Exec>
+ class AsioStdFileProvider
+ {
+ public:
+ using FileStreamT = AsioStdFileStream<Exec>;
+
+ /**
+ * Constructor.
+ *
+ * @param exec A boost::asio executor. It should run actions
+ * sequentially.
+ * @param filename The file name of this FileProvider.
+ */
+ inline AsioStdFileProvider(Exec exec, std::string filename)
+ : m_executor(exec)
+ , m_filename(filename)
+ {}
+
+ /**
+ * Get the FileStream provided by this.
+ *
+ * @return A FileStreamT that is associated with this file.
+ */
+ FileStreamT getStream(FileOpenMode mode) const {
+ return FileStreamT(m_executor, m_filename, mode);
+ }
+
+ private:
+ Exec m_executor;
+ std::string m_filename;
+ };
+
+ template<class Exec>
+ class AsioStdFileHandler
+ {
+ public:
+ using FileProviderT = AsioStdFileProvider<Exec>;
+
+ /**
+ * Constructor.
+ *
+ * @param exec A boost::asio executor. It should run actions
+ * sequentially.
+ */
+ inline AsioStdFileHandler(Exec exec)
+ : m_executor(exec)
+ {}
+
+ /**
+ * Get the FileProvider for `desc`.
+ *
+ * @warning You should not call this explicitly.
+ * It should only be called by `desc.provider(*this)`.
+ */
+ FileProviderT getProviderFor(FileDesc desc) const {
+ // assert(desc.name())
+ return FileProviderT(m_executor, desc.name().value());
+ }
+ private:
+ Exec m_executor;
+ };
+
+ template<class Exec>
+ AsioStdFileHandler(Exec)->AsioStdFileHandler<Exec>;
+}
diff --git a/src/base/file-desc.hpp b/src/base/file-desc.hpp
index a472627..eeb9c01 100644
--- a/src/base/file-desc.hpp
+++ b/src/base/file-desc.hpp
@@ -1,316 +1,340 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "libkazv-config.hpp"
#include <string>
#include <variant>
#include <immer/box.hpp>
#include <immer/flex_vector.hpp>
namespace Kazv
{
enum struct FileOpRetCode
{
Error,
Success,
Eof
};
+ enum struct FileOpenMode
+ {
+ Read,
+ Write
+ };
+
using FileContent = immer::flex_vector<char>;
class FileStream
{
public:
using DataT = FileContent;
/**
* Constructor.
*
* Construct a FileStream using `o`.
*
* `o` should be of a type that accepts the `read` and
* `write` methods in this class. That is,
* `o.read(int{}, std::function<void(FileOpRetCode, DataT)>{})`
* and `o.write(DataT{}, std::function<void(FileOpRetCode, int)>{})`
* must both be well-formed.
*/
template<class DeriveT>
FileStream(DeriveT &&o)
: m_d(std::make_unique<Model<std::decay_t<DeriveT>>>(
std::forward<DeriveT>(o))) {}
/**
* Read at most maxSize bytes from the stream.
*
* This calls `readCallback(FileOpRetCode::Success, data)` upon success,
* where `data` is a DataT containing the bytes read;
* calls `readCallback(FileOpRetCode::Eof, DataT{})` upon meeting EOF;
* and calls `readCallback(FileOpRetCode::Error, DataT{})` upon other errors.
*/
template<class Callback>
void read(int maxSize, Callback readCallback) { m_d->read(maxSize, readCallback); }
/**
* Write `data` into the stream.
*
* This calls `writeCallback(FileOpRetCode::Success, num)` upon success,
* where `num` is an integer that contains the number of bytes written.
* and calls `readCallback(FileOpRetCode::Error, 0)` upon other errors.
*/
template<class Callback>
void write(DataT data, Callback writeCallback) { m_d->write(data, writeCallback); }
private:
struct Concept
{
virtual ~Concept() = default;
using ReadCallback = std::function<void(FileOpRetCode, DataT)>;
using WriteCallback = std::function<void(FileOpRetCode, int)>;
virtual void read(int maxSize, ReadCallback callback) = 0;
virtual void write(DataT data, WriteCallback callback) = 0;
};
template<class DeriveT>
struct Model : public Concept
{
explicit Model(const DeriveT &o) : obj(o) {}
explicit Model(DeriveT &&o) : obj(std::move(o)) {}
~Model() override = default;
void read(int maxSize, ReadCallback callback) override {
return obj.read(maxSize, callback);
}
void write(DataT data, WriteCallback callback) override {
return obj.write(data, callback);
}
DeriveT obj;
};
std::unique_ptr<Concept> m_d;
};
class FileProvider
{
public:
/**
* Constructor.
*
* Construct a FileProvider using `o`.
*
* `o` should be of a copyable type `T` that has a method
- * `getStream()` that returns something implicitly convertible
+ * `getStream()` that takes a FileOpenMode
+ * and returns something implicitly convertible
* to FileStream.
*
- * That is, `[o]() -> FileStream { return o.getStream(); }`
+ * That is, `[o]() -> FileStream { return o.getStream(FileOpenMode::Read); }`
* must be well-formed.
+ *
+ * In addition, if the FileOpenMode passed to getStream is FileOpenMode::Read,
+ * the returned stream must support read(); if the FileOpenMode passed to getStream
+ * is FileOpenMode::Write, the returned stream must support write().
*/
template<class DeriveT>
FileProvider(DeriveT &&o)
: m_d(std::make_unique<Model<std::decay_t<DeriveT>>>(
std::forward<DeriveT>(o))) {}
inline FileProvider(const FileProvider &that) : m_d(that.m_d->clone()) {}
inline FileProvider(FileProvider &&that) : m_d(std::move(that.m_d)) {}
inline FileProvider &operator=(const FileProvider &that) {
m_d = that.m_d->clone();
return *this;
}
inline FileProvider &operator=(FileProvider &&that) {
m_d = std::move(that.m_d);
return *this;
}
/**
* Get the file stream provided by this.
*
* @return a FileStream that will contain the content
* of the file provided by this.
*/
- inline FileStream getStream() const { return m_d->getStream(); }
+ inline FileStream getStream(FileOpenMode mode = FileOpenMode::Read) const {
+ return m_d->getStream(mode);
+ }
private:
struct Concept
{
virtual ~Concept() = default;
virtual std::unique_ptr<Concept> clone() const = 0;
- virtual FileStream getStream() const = 0;
+ virtual FileStream getStream(FileOpenMode mode) const = 0;
};
template<class DeriveT>
struct Model : public Concept
{
explicit Model(const DeriveT &o) : obj(o) {}
explicit Model(DeriveT &&o) : obj(std::move(o)) {}
~Model() override = default;
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(obj);
}
- FileStream getStream() const override {
- return obj.getStream();
+ FileStream getStream(FileOpenMode mode) const override {
+ return obj.getStream(mode);
}
DeriveT obj;
};
std::unique_ptr<Concept> m_d;
};
class FileDesc;
class FileInterface;
struct DumbFileStream
{
inline explicit DumbFileStream(FileContent r) : remaining(r) {}
template<class Callback>
void read(int maxSize, Callback callback) {
if (remaining.empty()) {
callback(FileOpRetCode::Eof, remaining);
} else {
auto taken = remaining.take(maxSize);
remaining = remaining.drop(maxSize);
callback(FileOpRetCode::Success, taken);
}
}
template<class Callback>
void write(FileContent data, Callback callback) {
remaining = remaining + data;
callback(FileOpRetCode::Success, data.size());
}
FileContent remaining;
};
struct DumbFileProvider
{
inline explicit DumbFileProvider(FileContent content)
: m_content(content)
{}
- inline DumbFileStream getStream() const {
+ inline DumbFileStream getStream(FileOpenMode /* mode */) const {
return DumbFileStream(m_content);
}
FileContent m_content;
};
class FileDesc
{
public:
/**
* Construct an in-memory FileDesc with @c content.
*/
explicit FileDesc(FileContent content)
: m_inMemory{true}
, m_content(content)
{}
/**
* Construct a FileDesc with @c name and @c contentType.
*/
explicit FileDesc(std::string name,
std::optional<std::string> contentType = std::nullopt)
: m_inMemory{false}
, m_name(std::move(name))
, m_contentType(std::move(contentType)) {}
/**
* Get the FileProvider for this FileDesc using FileInterface.
*
* If this is an in-memory file, this will always return a
* DumbFileProvider. Otherwise, it will return what
* `fh.getProviderFor(*this)` returns.
*
* @return a FileProvider for this.
*/
FileProvider provider(const FileInterface &fh) const;
/**
* Get the name for this FileDesc.
*/
std::optional<std::string> name() const { return m_name; }
/**
* Get the content type for this FileDesc.
*/
std::optional<std::string> contentType() const { return m_contentType; }
private:
const bool m_inMemory;
FileContent m_content;
std::optional<std::string> m_name;
std::optional<std::string> m_contentType;
};
class DumbFileInterface
{
public:
DumbFileProvider getProviderFor(FileDesc desc) const;
};
class FileInterface
{
public:
+ /**
+ * Constructor.
+ *
+ * Construct a FileInterface using `o`.
+ *
+ * `o` should be of a type that has the getProviderFor()
+ * const method that takes a FileDesc and returns an object
+ * implicitly convertible to FileProvider. That is,
+ * the following should be well-formed:
+ * `[&o](FileDesc d) -> FileProvider { return static_cast<std::add_const_t<decltype(o)>>(o).getProviderFor(d); }`
+ */
template<class DeriveT>
inline FileInterface(DeriveT &&o)
: m_d(std::make_unique<Model<std::decay_t<DeriveT>>>(
std::forward<DeriveT>(o))) {}
/**
* Get the provider for @c desc from this.
*
* @warning: You should not call this directly. This
* function is called by FileDesc::provider().
*/
inline FileProvider getProviderFor(FileDesc desc) const {
return m_d->getProviderFor(std::move(desc));
}
private:
struct Concept
{
virtual ~Concept() = default;
virtual FileProvider getProviderFor(FileDesc desc) const = 0;
};
template<class DeriveT>
struct Model : public Concept
{
explicit Model(const DeriveT &o) : obj(o) {}
explicit Model(DeriveT &&o) : obj(std::move(o)) {}
~Model() override = default;
FileProvider getProviderFor(FileDesc desc) const override {
return obj.getProviderFor(std::move(desc));
}
DeriveT obj;
};
std::unique_ptr<Concept> m_d;
};
}
diff --git a/src/tests/file-desc-test.cpp b/src/tests/file-desc-test.cpp
index c71390b..a18edd6 100644
--- a/src/tests/file-desc-test.cpp
+++ b/src/tests/file-desc-test.cpp
@@ -1,96 +1,226 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <catch2/catch.hpp>
#include <boost/asio.hpp>
#include <vector>
#include <cstdio>
#include <fstream>
#include <filesystem>
#include "kazvtest-respath.hpp"
#include <file-desc.hpp>
+#include <asio-std-file-handler.hpp>
using namespace Kazv;
using ByteArrT = immer::flex_vector<char>;
TEST_CASE("FileDesc with DumbFileProvider should work properly", "[base][file-desc]")
{
auto desc = FileDesc(
immer::flex_vector<char>{'f', 'o', 'o', 'b', 'a', 'r'});
auto fh = FileInterface(DumbFileInterface{});
auto stream = desc.provider(fh).getStream();
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Success);
REQUIRE(data == ByteArrT{'f', 'o', 'o', 'b', 'a'});
});
stream.write(ByteArrT{'w', 'o', 'w'},
[](auto retCode, auto size) {
REQUIRE(retCode == FileOpRetCode::Success);
REQUIRE(size == 3);
});
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Success);
REQUIRE(data == ByteArrT{'r', 'w', 'o', 'w'});
});
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Eof);
REQUIRE(data == ByteArrT{});
});
}
TEST_CASE("DumbFileInterface should work properly", "[base][file-desc]")
{
auto name1 = std::filesystem::path(resPath) / "file-desc-test-res1";
auto desc = FileDesc(name1.native());
auto fh = FileInterface{DumbFileInterface{}};
auto provider = desc.provider(fh);
auto stream = provider.getStream();
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Success);
REQUIRE(data == ByteArrT{'f', 'o', 'o', 'b', 'a'});
});
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Success);
REQUIRE(data == ByteArrT{'r'});
});
stream.read(5, [](auto retCode, auto data) {
REQUIRE(retCode == FileOpRetCode::Eof);
REQUIRE(data == ByteArrT{});
});
}
+
+TEST_CASE("AsioStdFileHandler should work properly with reads", "[base][file-desc]")
+{
+ auto name1 = std::filesystem::path(resPath) / "file-desc-test-res1";
+
+ auto desc = FileDesc(name1.native());
+
+ auto io = boost::asio::io_context();
+
+ auto fh = FileInterface{AsioStdFileHandler{io.get_executor()}};
+ auto provider = desc.provider(fh);
+
+ auto stream = provider.getStream();
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(data == ByteArrT{'f', 'o', 'o', 'b', 'a'});
+ });
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(data == ByteArrT{'r'});
+ });
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Eof);
+ REQUIRE(data == ByteArrT{});
+ });
+
+ io.run();
+}
+
+struct FileGuard
+{
+ ~FileGuard() {
+ std::filesystem::remove(m_filename);
+ }
+ std::filesystem::path m_filename;
+};
+
+TEST_CASE("AsioStdFileHandler should work properly with writes", "[base][file-desc]")
+{
+ auto name1 = std::filesystem::path(resPath) / "file-desc-test-tmp1";
+
+ auto guard = FileGuard{name1};
+
+ auto desc = FileDesc(name1.native());
+
+ auto io = boost::asio::io_context();
+
+ auto fh = FileInterface{AsioStdFileHandler{io.get_executor()}};
+ auto provider = desc.provider(fh);
+
+ {
+ auto stream = provider.getStream(FileOpenMode::Write);
+
+ stream.write(ByteArrT{'f', 'o', 'o'},
+ [](auto retCode, auto count) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(count == 3);
+ });
+
+ stream.write(ByteArrT{'b', 'a', 'r'},
+ [](auto retCode, auto count) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(count == 3);
+ });
+ }
+
+ io.run(); // wait for the write to finish, and destroy the stream.
+
+ auto stream = provider.getStream(FileOpenMode::Read);
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(data == ByteArrT{'f', 'o', 'o', 'b', 'a'});
+ });
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(data == ByteArrT{'r'});
+ });
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Eof);
+ REQUIRE(data == ByteArrT{});
+ });
+
+ io.run();
+}
+
+
+TEST_CASE("AsioStdFileHandler should work properly with binary data", "[base][file-desc]")
+{
+ auto name1 = std::filesystem::path(resPath) / "file-desc-test-tmp2";
+
+ auto guard = FileGuard{name1};
+
+ auto desc = FileDesc(name1.native());
+
+ auto io = boost::asio::io_context();
+
+ auto fh = FileInterface{AsioStdFileHandler{io.get_executor()}};
+ auto provider = desc.provider(fh);
+
+ {
+ auto stream = provider.getStream(FileOpenMode::Write);
+
+ stream.write(ByteArrT{'\0', '\r', '\n', '\n', '\r'},
+ [](auto retCode, auto count) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(count == 5);
+ });
+ }
+
+ io.run(); // wait for the write to finish, and destroy the stream.
+
+ auto stream = provider.getStream(FileOpenMode::Read);
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Success);
+ REQUIRE(data == ByteArrT{'\0', '\r', '\n', '\n', '\r'});
+ });
+
+ stream.read(5, [](auto retCode, auto data) {
+ REQUIRE(retCode == FileOpRetCode::Eof);
+ REQUIRE(data == ByteArrT{});
+ });
+
+ io.run();
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 7:45 PM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55421
Default Alt Text
(24 KB)

Event Timeline