1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-30 18:08:12 +00:00
serenity/Userland/Services/FileSystemAccessServer/ClientConnection.cpp
Timothy 38594dde79 FileSystemAccessServer+TextEditor: Implement cross-process modal prompts
This transitions from synchronous IPC calls to asynchronous IPC calls
provided through a synchronous interface in LibFileSystemAccessClient
which allows the parent Application to stay responsive.

It achieves this with Promise which is pumping the Application event
loop while waiting for the Dialog to respond with the user's action.

LibFileSystemAccessClient provides a lazy singleton which also ensures
that FileSystemAccessServer is running in the event of a crash.

This also transitions TextEditor into using LibFileSystemAccessClient.
2021-07-18 17:21:28 +02:00

160 lines
6.2 KiB
C++

/*
* Copyright (c) 2021, timmot <tiwwot@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
// FIXME: LibIPC Decoder and Encoder are sensitive to include order here
// clang-format off
#include <LibGUI/WindowServerConnection.h>
// clang-format on
#include <AK/Debug.h>
#include <FileSystemAccessServer/ClientConnection.h>
#include <LibCore/File.h>
#include <LibCore/IODevice.h>
#include <LibGUI/Application.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/MessageBox.h>
namespace FileSystemAccessServer {
static HashMap<int, NonnullRefPtr<ClientConnection>> s_connections;
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> socket, int client_id)
: IPC::ClientConnection<FileSystemAccessClientEndpoint, FileSystemAccessServerEndpoint>(*this, move(socket), client_id)
{
s_connections.set(client_id, *this);
}
ClientConnection::~ClientConnection()
{
}
void ClientConnection::die()
{
s_connections.remove(client_id());
GUI::Application::the()->quit();
exit(0);
}
RefPtr<GUI::Window> ClientConnection::create_dummy_child_window(i32 window_server_client_id, i32 parent_window_id)
{
auto window = GUI::Window::construct();
window->set_opacity(0);
window->set_frameless(true);
auto rect = GUI::WindowServerConnection::the().get_window_rect_from_client(window_server_client_id, parent_window_id);
window->set_rect(rect);
window->show();
GUI::WindowServerConnection::the().async_set_window_parent_from_client(window_server_client_id, parent_window_id, window->window_id());
return window;
}
void ClientConnection::request_file(i32 window_server_client_id, i32 parent_window_id, String const& path, Core::OpenMode const& requested_access)
{
VERIFY(path.starts_with("/"sv));
bool approved = false;
auto maybe_permissions = m_approved_files.get(path);
auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly);
VERIFY(relevant_permissions != Core::OpenMode::NotOpen);
if (maybe_permissions.has_value())
approved = has_flag(maybe_permissions.value(), relevant_permissions);
if (!approved) {
String access_string;
if (has_flag(requested_access, Core::OpenMode::ReadWrite))
access_string = "read and write";
else if (has_flag(requested_access, Core::OpenMode::ReadOnly))
access_string = "read from";
else if (has_flag(requested_access, Core::OpenMode::WriteOnly))
access_string = "write to";
auto pid = this->socket().peer_pid();
auto exe_link = LexicalPath("/proc").append(String::number(pid)).append("exe").string();
auto exe_path = Core::File::real_path_for(exe_link);
auto exe_name = LexicalPath::basename(exe_path);
auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id);
auto result = GUI::MessageBox::show(main_window, String::formatted("Allow {} ({}) to {} \"{}\"?", exe_name, pid, access_string, path), "File Permissions Requested", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::YesNo);
approved = result == GUI::MessageBox::ExecYes;
if (approved) {
auto new_permissions = relevant_permissions;
if (maybe_permissions.has_value())
new_permissions |= maybe_permissions.value();
m_approved_files.set(path, new_permissions);
}
}
if (approved) {
auto file = Core::File::open(path, requested_access);
if (file.is_error()) {
dbgln("FileSystemAccessServer: Couldn't open {}, error {}", path, file.error());
async_handle_prompt_end(errno, Optional<IPC::File> {}, path);
} else {
async_handle_prompt_end(0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), path);
}
} else {
async_handle_prompt_end(-1, Optional<IPC::File> {}, path);
}
}
void ClientConnection::prompt_open_file(i32 window_server_client_id, i32 parent_window_id, String const& path_to_view, Core::OpenMode const& requested_access)
{
auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly);
VERIFY(relevant_permissions != Core::OpenMode::NotOpen);
auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id);
auto user_picked_file = GUI::FilePicker::get_open_filepath(main_window, "Select file", path_to_view);
prompt_helper(user_picked_file, requested_access);
}
void ClientConnection::prompt_save_file(i32 window_server_client_id, i32 parent_window_id, String const& name, String const& ext, String const& path_to_view, Core::OpenMode const& requested_access)
{
auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly);
VERIFY(relevant_permissions != Core::OpenMode::NotOpen);
auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id);
auto user_picked_file = GUI::FilePicker::get_save_filepath(main_window, name, ext, path_to_view);
prompt_helper(user_picked_file, requested_access);
}
void ClientConnection::prompt_helper(Optional<String> const& user_picked_file, Core::OpenMode const& requested_access)
{
if (user_picked_file.has_value()) {
VERIFY(user_picked_file->starts_with("/"sv));
auto file = Core::File::open(user_picked_file.value(), requested_access);
if (file.is_error()) {
dbgln("FileSystemAccessServer: Couldn't open {}, error {}", user_picked_file.value(), file.error());
async_handle_prompt_end(errno, Optional<IPC::File> {}, user_picked_file);
} else {
auto maybe_permissions = m_approved_files.get(user_picked_file.value());
auto new_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly);
if (maybe_permissions.has_value())
new_permissions |= maybe_permissions.value();
m_approved_files.set(user_picked_file.value(), new_permissions);
async_handle_prompt_end(0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), user_picked_file);
}
} else {
async_handle_prompt_end(-1, Optional<IPC::File> {}, Optional<String> {});
}
}
}