feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys
Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low- latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775, volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB the standalone no-renderer build needs; scripts/vendor-pyrowave.sh reproduces the tree (a pin bump is protocol-affecting, plan §4.2). build.rs drives the wrapper CMakeLists (static archives incl. a static C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7). Offline-safe by construction: no network, no system lib, vendored Vulkan headers — same model as the opus dep (flatpak builder has no network). Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03): - upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier Vulkan<->Vulkan) pass, from the pristine AND the pruned tree - GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p, 0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of target - cargo test -p pyrowave-sys green (static link + API-version pin check) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,895 @@
|
||||
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "fs-netfs.hpp"
|
||||
#include "path_utils.hpp"
|
||||
#include "logging.hpp"
|
||||
#include <assert.h>
|
||||
#include <queue>
|
||||
|
||||
#define HOST_IP "localhost"
|
||||
|
||||
namespace Granite
|
||||
{
|
||||
struct FSNotifyCommand : LooperHandler
|
||||
{
|
||||
FSNotifyCommand(const string &protocol, unique_ptr<Socket> socket_)
|
||||
: LooperHandler(move(socket_)), expected(false)
|
||||
{
|
||||
reply_queue.emplace();
|
||||
auto &reply = reply_queue.back();
|
||||
reply.builder.add_u32(NETFS_NOTIFICATION);
|
||||
reply.builder.add_u32(NETFS_BEGIN_CHUNK_REQUEST);
|
||||
reply.builder.add_string(protocol);
|
||||
reply.writer.start(reply.builder.get_buffer());
|
||||
|
||||
result_reply.begin(4 * sizeof(uint32_t));
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
|
||||
state = NotificationLoop;
|
||||
}
|
||||
|
||||
void expected_destruction()
|
||||
{
|
||||
expected = true;
|
||||
}
|
||||
|
||||
~FSNotifyCommand()
|
||||
{
|
||||
if (!expected)
|
||||
terminate();
|
||||
}
|
||||
|
||||
void set_notify_cb(function<void (const FileNotifyInfo &)> func)
|
||||
{
|
||||
notify_cb = move(func);
|
||||
}
|
||||
|
||||
void push_register_notification(const string &path, promise<FileNotifyHandle> result)
|
||||
{
|
||||
if (reply_queue.empty() && socket->get_parent_looper())
|
||||
socket->get_parent_looper()->modify_handler(EVENT_IN | EVENT_OUT, *this);
|
||||
|
||||
reply_queue.emplace();
|
||||
auto &reply = reply_queue.back();
|
||||
reply.builder.add_u32(NETFS_REGISTER_NOTIFICATION);
|
||||
reply.builder.add_string(path);
|
||||
reply.writer.start(reply.builder.get_buffer());
|
||||
|
||||
replies.push(move(result));
|
||||
}
|
||||
|
||||
void push_unregister_notification(FileNotifyHandle handler, promise<FileNotifyHandle> result)
|
||||
{
|
||||
if (reply_queue.empty() && socket->get_parent_looper())
|
||||
socket->get_parent_looper()->modify_handler(EVENT_IN | EVENT_OUT, *this);
|
||||
|
||||
reply_queue.emplace();
|
||||
auto &reply = reply_queue.back();
|
||||
reply.builder.add_u32(NETFS_UNREGISTER_NOTIFICATION);
|
||||
reply.builder.add_u64(8);
|
||||
reply.builder.add_u64(uint64_t(handler));
|
||||
reply.writer.start(reply.builder.get_buffer());
|
||||
replies.push(move(result));
|
||||
}
|
||||
|
||||
void modify_looper(Looper &looper)
|
||||
{
|
||||
uint32_t mask = reply_queue.empty() ? EVENT_IN : (EVENT_IN | EVENT_OUT);
|
||||
looper.modify_handler(mask, *this);
|
||||
}
|
||||
|
||||
bool read_reply_data(Looper &looper)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
if (command_reader.complete())
|
||||
{
|
||||
if (last_cmd == NETFS_BEGIN_CHUNK_NOTIFICATION)
|
||||
{
|
||||
FileNotifyInfo info;
|
||||
info.path = result_reply.read_string();
|
||||
info.handle = FileNotifyHandle(result_reply.read_u64());
|
||||
auto type = result_reply.read_u32();
|
||||
switch (type)
|
||||
{
|
||||
case NETFS_FILE_CHANGED:
|
||||
info.type = FileNotifyType::FileChanged;
|
||||
break;
|
||||
case NETFS_FILE_DELETED:
|
||||
info.type = FileNotifyType::FileDeleted;
|
||||
break;
|
||||
case NETFS_FILE_CREATED:
|
||||
info.type = FileNotifyType::FileCreated;
|
||||
break;
|
||||
}
|
||||
|
||||
notify_cb(info);
|
||||
result_reply.begin(4 * sizeof(uint32_t));
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
modify_looper(looper);
|
||||
state = NotificationLoop;
|
||||
return true;
|
||||
}
|
||||
else if (last_cmd == NETFS_BEGIN_CHUNK_REPLY)
|
||||
{
|
||||
auto handle = int(result_reply.read_u64());
|
||||
|
||||
try
|
||||
{
|
||||
replies.front().set_value(handle);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
assert(!replies.empty());
|
||||
replies.pop();
|
||||
|
||||
result_reply.begin(4 * sizeof(uint32_t));
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
modify_looper(looper);
|
||||
state = NotificationLoop;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
bool notification_loop(Looper &looper, EventFlags flags)
|
||||
{
|
||||
if (flags & EVENT_OUT)
|
||||
{
|
||||
if (reply_queue.empty())
|
||||
{
|
||||
looper.modify_handler(EVENT_IN, *this);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ret = reply_queue.front().writer.process(*socket);
|
||||
if (reply_queue.front().writer.complete())
|
||||
reply_queue.pop();
|
||||
|
||||
if (reply_queue.empty())
|
||||
{
|
||||
looper.modify_handler(EVENT_IN, *this);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
if (flags & EVENT_IN)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
if (command_reader.complete())
|
||||
{
|
||||
auto cmd = result_reply.read_u32();
|
||||
if (cmd == NETFS_BEGIN_CHUNK_NOTIFICATION || cmd == NETFS_BEGIN_CHUNK_REPLY)
|
||||
{
|
||||
if (result_reply.read_u32() != NETFS_ERROR_OK)
|
||||
return false;
|
||||
|
||||
last_cmd = cmd;
|
||||
auto size = result_reply.read_u64();
|
||||
|
||||
if (size)
|
||||
{
|
||||
// Either receive notification or acknowledgement.
|
||||
result_reply.begin(size);
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
state = ReadReplyData;
|
||||
looper.modify_handler(EVENT_IN, *this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Acknowledge unregister notification.
|
||||
try
|
||||
{
|
||||
replies.front().set_value(0);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
assert(!replies.empty());
|
||||
replies.pop();
|
||||
|
||||
result_reply.begin(4 * sizeof(uint32_t));
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
modify_looper(looper);
|
||||
state = NotificationLoop;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(Looper &looper, EventFlags flags) override
|
||||
{
|
||||
if (state == ReadReplyData)
|
||||
return read_reply_data(looper);
|
||||
else if (state == NotificationLoop)
|
||||
return notification_loop(looper, flags);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
ReadReplyData,
|
||||
NotificationLoop
|
||||
};
|
||||
|
||||
State state = NotificationLoop;
|
||||
SocketReader command_reader;
|
||||
ReplyBuilder result_reply;
|
||||
uint32_t last_cmd = 0;
|
||||
|
||||
struct NotificationReply
|
||||
{
|
||||
SocketWriter writer;
|
||||
ReplyBuilder builder;
|
||||
};
|
||||
queue<NotificationReply> reply_queue;
|
||||
queue<promise<FileNotifyHandle>> replies;
|
||||
function<void (const FileNotifyInfo &info)> notify_cb;
|
||||
atomic_bool expected;
|
||||
};
|
||||
|
||||
struct FSReadCommand : LooperHandler
|
||||
{
|
||||
virtual ~FSReadCommand() = default;
|
||||
|
||||
FSReadCommand(const string &path, NetFSCommand command, unique_ptr<Socket> socket_)
|
||||
: LooperHandler(move(socket_))
|
||||
{
|
||||
reply_builder.begin();
|
||||
reply_builder.add_u32(command);
|
||||
reply_builder.add_u32(NETFS_BEGIN_CHUNK_REQUEST);
|
||||
reply_builder.add_string(path);
|
||||
command_writer.start(reply_builder.get_buffer());
|
||||
state = WriteCommand;
|
||||
}
|
||||
|
||||
bool write_command(Looper &looper)
|
||||
{
|
||||
auto ret = command_writer.process(*socket);
|
||||
if (command_writer.complete())
|
||||
{
|
||||
state = ReadReplySize;
|
||||
reply_builder.begin(4 * sizeof(uint32_t));
|
||||
command_reader.start(reply_builder.get_buffer());
|
||||
looper.modify_handler(EVENT_IN, *this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
bool read_reply_size(Looper &)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
if (command_reader.complete())
|
||||
{
|
||||
if (reply_builder.read_u32() != NETFS_BEGIN_CHUNK_REPLY)
|
||||
return false;
|
||||
|
||||
if (reply_builder.read_u32() != NETFS_ERROR_OK)
|
||||
return false;
|
||||
|
||||
uint64_t reply_size = reply_builder.read_u64();
|
||||
if (reply_size == 0)
|
||||
return false;
|
||||
|
||||
reply_builder.begin(reply_size);
|
||||
command_reader.start(reply_builder.get_buffer());
|
||||
state = ReadReply;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
bool read_reply(Looper &)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
if (command_reader.complete())
|
||||
{
|
||||
parse_reply();
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
bool handle(Looper &looper, EventFlags) override
|
||||
{
|
||||
if (state == WriteCommand)
|
||||
return write_command(looper);
|
||||
else if (state == ReadReplySize)
|
||||
return read_reply_size(looper);
|
||||
else if (state == ReadReply)
|
||||
return read_reply(looper);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
WriteCommand,
|
||||
ReadReplySize,
|
||||
ReadReply
|
||||
};
|
||||
State state = WriteCommand;
|
||||
SocketReader command_reader;
|
||||
SocketWriter command_writer;
|
||||
ReplyBuilder reply_builder;
|
||||
|
||||
virtual void parse_reply() = 0;
|
||||
};
|
||||
|
||||
struct FSReader : FSReadCommand
|
||||
{
|
||||
FSReader(const string &path, unique_ptr<Socket> socket_)
|
||||
: FSReadCommand(path, NETFS_READ_FILE, move(socket_))
|
||||
{
|
||||
}
|
||||
|
||||
~FSReader()
|
||||
{
|
||||
if (!got_reply)
|
||||
result.set_exception(make_exception_ptr(runtime_error("file read")));
|
||||
}
|
||||
|
||||
void parse_reply() override
|
||||
{
|
||||
got_reply = true;
|
||||
try
|
||||
{
|
||||
result.set_value(reply_builder.consume_buffer());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
promise<vector<uint8_t>> result;
|
||||
bool got_reply = false;
|
||||
};
|
||||
|
||||
struct FSList : FSReadCommand
|
||||
{
|
||||
FSList(const string &path, unique_ptr<Socket> socket_)
|
||||
: FSReadCommand(path, NETFS_LIST, move(socket_))
|
||||
{
|
||||
}
|
||||
|
||||
~FSList()
|
||||
{
|
||||
if (!got_reply)
|
||||
result.set_exception(make_exception_ptr(runtime_error("List failed")));
|
||||
}
|
||||
|
||||
void parse_reply() override
|
||||
{
|
||||
uint32_t entries = reply_builder.read_u32();
|
||||
vector<ListEntry> list;
|
||||
for (uint32_t i = 0; i < entries; i++)
|
||||
{
|
||||
auto path = reply_builder.read_string();
|
||||
auto type = reply_builder.read_u32();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NETFS_FILE_TYPE_PLAIN:
|
||||
list.push_back({ move(path), PathType::File });
|
||||
break;
|
||||
case NETFS_FILE_TYPE_DIRECTORY:
|
||||
list.push_back({ move(path), PathType::Directory });
|
||||
break;
|
||||
case NETFS_FILE_TYPE_SPECIAL:
|
||||
list.push_back({ move(path), PathType::Special });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
got_reply = true;
|
||||
try
|
||||
{
|
||||
result.set_value(move(list));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
promise<vector<ListEntry>> result;
|
||||
bool got_reply = false;
|
||||
};
|
||||
|
||||
struct FSStat : FSReadCommand
|
||||
{
|
||||
FSStat(const string &path, unique_ptr<Socket> socket_)
|
||||
: FSReadCommand(path, NETFS_STAT, move(socket_))
|
||||
{
|
||||
}
|
||||
|
||||
~FSStat()
|
||||
{
|
||||
// Throw exception instead in calling thread.
|
||||
if (!got_reply)
|
||||
result.set_exception(make_exception_ptr(runtime_error("Failed stat")));
|
||||
}
|
||||
|
||||
void parse_reply() override
|
||||
{
|
||||
uint64_t size = reply_builder.read_u64();
|
||||
uint32_t type = reply_builder.read_u32();
|
||||
uint64_t last_modified = reply_builder.read_u64();
|
||||
FileStat s;
|
||||
s.size = size;
|
||||
s.last_modified = last_modified;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NETFS_FILE_TYPE_PLAIN:
|
||||
s.type = PathType::File;
|
||||
break;
|
||||
case NETFS_FILE_TYPE_DIRECTORY:
|
||||
s.type = PathType::Directory;
|
||||
break;
|
||||
case NETFS_FILE_TYPE_SPECIAL:
|
||||
s.type = PathType::Special;
|
||||
break;
|
||||
}
|
||||
|
||||
got_reply = true;
|
||||
try
|
||||
{
|
||||
result.set_value(s);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
std::promise<FileStat> result;
|
||||
bool got_reply = false;
|
||||
};
|
||||
|
||||
struct FSWriteCommand : LooperHandler
|
||||
{
|
||||
FSWriteCommand(const string &path, const vector<uint8_t> &buffer, unique_ptr<Socket> socket_)
|
||||
: LooperHandler(move(socket_))
|
||||
{
|
||||
target_size = buffer.size();
|
||||
|
||||
reply_builder.begin();
|
||||
result_reply.begin(4 * sizeof(uint32_t));
|
||||
|
||||
reply_builder.add_u32(NETFS_WRITE_FILE);
|
||||
reply_builder.add_u32(NETFS_BEGIN_CHUNK_REQUEST);
|
||||
reply_builder.add_string(path);
|
||||
reply_builder.add_u32(NETFS_BEGIN_CHUNK_REQUEST);
|
||||
reply_builder.add_u64(buffer.size());
|
||||
reply_builder.add_buffer(buffer);
|
||||
command_writer.start(reply_builder.get_buffer());
|
||||
command_reader.start(result_reply.get_buffer());
|
||||
state = WriteCommand;
|
||||
}
|
||||
|
||||
bool write_command(Looper &looper, EventFlags flags)
|
||||
{
|
||||
if (flags & EVENT_IN)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
// Received message before we completed the write, must be an error.
|
||||
if (command_reader.complete())
|
||||
return false;
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
else if (flags & EVENT_OUT)
|
||||
{
|
||||
auto ret = command_writer.process(*socket);
|
||||
if (command_writer.complete())
|
||||
{
|
||||
// Done writing, wait for reply.
|
||||
looper.modify_handler(EVENT_IN, *this);
|
||||
state = ReadReply;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
~FSWriteCommand()
|
||||
{
|
||||
if (!got_reply)
|
||||
result.set_exception(make_exception_ptr(runtime_error("Failed write")));
|
||||
}
|
||||
|
||||
bool read_reply(Looper &)
|
||||
{
|
||||
auto ret = command_reader.process(*socket);
|
||||
if (command_reader.complete())
|
||||
{
|
||||
if (result_reply.read_u32() != NETFS_BEGIN_CHUNK_REPLY)
|
||||
return false;
|
||||
if (result_reply.read_u32() != NETFS_ERROR_OK)
|
||||
return false;
|
||||
if (result_reply.read_u64() != target_size)
|
||||
return false;
|
||||
|
||||
got_reply = true;
|
||||
try
|
||||
{
|
||||
result.set_value(NETFS_ERROR_OK);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ret > 0) || (ret == Socket::ErrorWouldBlock);
|
||||
}
|
||||
|
||||
bool handle(Looper &looper, EventFlags flags) override
|
||||
{
|
||||
if (state == WriteCommand)
|
||||
return write_command(looper, flags);
|
||||
else if (state == ReadReply)
|
||||
return read_reply(looper);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
WriteCommand,
|
||||
ReadReply
|
||||
};
|
||||
|
||||
State state = WriteCommand;
|
||||
SocketReader command_reader;
|
||||
SocketWriter command_writer;
|
||||
ReplyBuilder reply_builder;
|
||||
ReplyBuilder result_reply;
|
||||
size_t target_size = 0;
|
||||
|
||||
promise<NetFSError> result;
|
||||
bool got_reply = false;
|
||||
};
|
||||
|
||||
NetworkFilesystem::NetworkFilesystem()
|
||||
{
|
||||
looper_thread = thread(&NetworkFilesystem::looper_entry, this);
|
||||
}
|
||||
|
||||
void NetworkFilesystem::looper_entry()
|
||||
{
|
||||
while (looper.wait_idle(-1) >= 0);
|
||||
}
|
||||
|
||||
void NetworkFilesystem::setup_notification()
|
||||
{
|
||||
auto socket = Socket::connect(HOST_IP, 7070);
|
||||
if (!socket)
|
||||
return;
|
||||
notify = new FSNotifyCommand(protocol, move(socket));
|
||||
notify->set_notify_cb([this](const FileNotifyInfo &info) {
|
||||
signal_notification(info);
|
||||
});
|
||||
|
||||
// Move capture would be nice ...
|
||||
looper.run_in_looper([this]() {
|
||||
looper.register_handler(EVENT_OUT, unique_ptr<FSNotifyCommand>(notify));
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkFilesystem::uninstall_notification(FileNotifyHandle handle)
|
||||
{
|
||||
if (!notify)
|
||||
setup_notification();
|
||||
if (!notify)
|
||||
return;
|
||||
|
||||
auto itr = handlers.find(handle);
|
||||
if (itr == end(handlers))
|
||||
return;
|
||||
handlers.erase(itr);
|
||||
|
||||
auto *value = new promise<FileNotifyHandle>;
|
||||
auto result = value->get_future();
|
||||
looper.run_in_looper([this, value, handle]() {
|
||||
notify->push_unregister_notification(handle, move(*value));
|
||||
delete value;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
result.wait();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkFilesystem::signal_notification(const FileNotifyInfo &info)
|
||||
{
|
||||
lock_guard<mutex> holder{lock};
|
||||
pending.push_back(info);
|
||||
}
|
||||
|
||||
void NetworkFilesystem::poll_notifications()
|
||||
{
|
||||
vector<FileNotifyInfo> tmp_pending;
|
||||
{
|
||||
lock_guard<mutex> holder{lock};
|
||||
swap(tmp_pending, pending);
|
||||
}
|
||||
|
||||
for (auto ¬ification : tmp_pending)
|
||||
{
|
||||
auto &func = handlers[notification.handle];
|
||||
if (func)
|
||||
func(notification);
|
||||
}
|
||||
}
|
||||
|
||||
FileNotifyHandle NetworkFilesystem::install_notification(const std::string &path,
|
||||
std::function<void(const FileNotifyInfo &)> func)
|
||||
{
|
||||
if (!notify)
|
||||
setup_notification();
|
||||
if (!notify)
|
||||
return -1;
|
||||
|
||||
auto *value = new promise<FileNotifyHandle>;
|
||||
auto result = value->get_future();
|
||||
|
||||
looper.run_in_looper([this, value, path]() {
|
||||
notify->push_register_notification(path, move(*value));
|
||||
delete value;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
auto handle = result.get();
|
||||
handlers[handle] = move(func);
|
||||
return handle;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
vector<ListEntry> NetworkFilesystem::list(const std::string &path)
|
||||
{
|
||||
auto joined = protocol + "://" + path;
|
||||
auto socket = Socket::connect(HOST_IP, 7070);
|
||||
if (!socket)
|
||||
return {};
|
||||
|
||||
unique_ptr<FSList> handler(new FSList(joined, move(socket)));
|
||||
auto fut = handler->result.get_future();
|
||||
|
||||
looper.run_in_looper([&]() {
|
||||
looper.register_handler(EVENT_OUT, move(handler));
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
return fut.get();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
NetworkFile::~NetworkFile()
|
||||
{
|
||||
unmap();
|
||||
}
|
||||
|
||||
NetworkFile *NetworkFile::open(Granite::Looper &looper, const std::string &path, Granite::FileMode mode)
|
||||
{
|
||||
auto *file = new NetworkFile;
|
||||
if (!file->init(looper, path, mode))
|
||||
{
|
||||
delete file;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NetworkFile::init(Looper &looper_, const std::string &path_, FileMode mode_)
|
||||
{
|
||||
path = path_;
|
||||
mode = mode_;
|
||||
looper = &looper_;
|
||||
|
||||
if (mode == FileMode::ReadWrite)
|
||||
{
|
||||
LOGE("Unsupported file mode.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == FileMode::ReadOnly)
|
||||
{
|
||||
if (!reopen())
|
||||
{
|
||||
LOGE("Failed to connect to server.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkFile::unmap()
|
||||
{
|
||||
if (mode == FileMode::WriteOnly && has_buffer && need_flush)
|
||||
{
|
||||
need_flush = false;
|
||||
auto socket = Socket::connect(HOST_IP, 7070);
|
||||
if (!socket)
|
||||
throw runtime_error("Failed to connect to server.");
|
||||
|
||||
auto handler = unique_ptr<FSWriteCommand>(new FSWriteCommand(path, buffer, move(socket)));
|
||||
auto reply = handler->result.get_future();
|
||||
looper->run_in_looper([&handler, this]() {
|
||||
looper->register_handler(EVENT_OUT | EVENT_IN, move(handler));
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
NetFSError error = reply.get();
|
||||
if (error != NETFS_ERROR_OK)
|
||||
LOGE("Failed to write file: %s\n", path.c_str());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOGE("Failed to write file: %s\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkFile::reopen()
|
||||
{
|
||||
if (mode == FileMode::ReadOnly)
|
||||
{
|
||||
has_buffer = false;
|
||||
auto socket = Socket::connect(HOST_IP, 7070);
|
||||
if (!socket)
|
||||
return false;
|
||||
|
||||
auto *handler = new FSReader(path, move(socket));
|
||||
future = handler->result.get_future();
|
||||
|
||||
// Capture-by-move would be nice here.
|
||||
looper->run_in_looper([handler, this]() {
|
||||
looper->register_handler(EVENT_OUT, unique_ptr<FSReader>(handler));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void *NetworkFile::map_write(size_t size)
|
||||
{
|
||||
has_buffer = true;
|
||||
need_flush = true;
|
||||
buffer.resize(size);
|
||||
return buffer.empty() ? nullptr : buffer.data();
|
||||
}
|
||||
|
||||
void *NetworkFile::map()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!has_buffer)
|
||||
{
|
||||
buffer = future.get();
|
||||
has_buffer = true;
|
||||
}
|
||||
return buffer.empty() ? nullptr : buffer.data();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t NetworkFile::get_size()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!has_buffer)
|
||||
{
|
||||
buffer = future.get();
|
||||
has_buffer = true;
|
||||
}
|
||||
return buffer.size();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<File> NetworkFilesystem::open(const std::string &path, FileMode mode)
|
||||
{
|
||||
auto joined = protocol + "://" + path;
|
||||
return unique_ptr<File>(NetworkFile::open(looper, move(joined), mode));
|
||||
}
|
||||
|
||||
bool NetworkFilesystem::stat(const std::string &path, FileStat &stat)
|
||||
{
|
||||
auto joined = protocol + "://" + path;
|
||||
auto socket = Socket::connect(HOST_IP, 7070);
|
||||
if (!socket)
|
||||
return false;
|
||||
|
||||
unique_ptr<FSStat> handler(new FSStat(joined, move(socket)));
|
||||
auto fut = handler->result.get_future();
|
||||
|
||||
looper.run_in_looper([&]() {
|
||||
looper.register_handler(EVENT_OUT, move(handler));
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
stat = fut.get();
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkFilesystem::~NetworkFilesystem()
|
||||
{
|
||||
if (notify)
|
||||
notify->expected_destruction();
|
||||
|
||||
looper.kill();
|
||||
if (looper_thread.joinable())
|
||||
looper_thread.join();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user