Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140405
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
24 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment