Serenity Operating System
at master 178 lines 7.8 kB view raw
1/* 2 * Copyright (c) 2021, timmot <tiwwot@protonmail.com> 3 * Copyright (c) 2022, Mustafa Quraish <mustafa@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/LexicalPath.h> 9#include <LibCore/DeprecatedFile.h> 10#include <LibFileSystemAccessClient/Client.h> 11#include <LibGUI/ConnectionToWindowServer.h> 12#include <LibGUI/MessageBox.h> 13#include <LibGUI/Window.h> 14 15namespace FileSystemAccessClient { 16 17static RefPtr<Client> s_the = nullptr; 18 19Client& Client::the() 20{ 21 if (!s_the || !s_the->is_open()) 22 s_the = Client::try_create().release_value_but_fixme_should_propagate_errors(); 23 return *s_the; 24} 25 26Result Client::request_file_read_only_approved(GUI::Window* parent_window, DeprecatedString const& path) 27{ 28 auto const id = get_new_id(); 29 m_promises.set(id, PromiseAndWindow { { Core::Promise<Result>::construct() }, parent_window }); 30 31 auto parent_window_server_client_id = GUI::ConnectionToWindowServer::the().expose_client_id(); 32 auto child_window_server_client_id = expose_window_server_client_id(); 33 auto parent_window_id = parent_window->window_id(); 34 35 GUI::ConnectionToWindowServer::the().add_window_stealing_for_client(child_window_server_client_id, parent_window_id); 36 37 ScopeGuard guard([parent_window_id, child_window_server_client_id] { 38 GUI::ConnectionToWindowServer::the().remove_window_stealing_for_client(child_window_server_client_id, parent_window_id); 39 }); 40 41 if (path.starts_with('/')) { 42 async_request_file_read_only_approved(id, parent_window_server_client_id, parent_window_id, path); 43 } else { 44 auto full_path = LexicalPath::join(Core::DeprecatedFile::current_working_directory(), path).string(); 45 async_request_file_read_only_approved(id, parent_window_server_client_id, parent_window_id, full_path); 46 } 47 48 return handle_promise(id); 49} 50 51Result Client::request_file(GUI::Window* parent_window, DeprecatedString const& path, Core::File::OpenMode mode) 52{ 53 auto const id = get_new_id(); 54 m_promises.set(id, PromiseAndWindow { { Core::Promise<Result>::construct() }, parent_window }); 55 56 auto parent_window_server_client_id = GUI::ConnectionToWindowServer::the().expose_client_id(); 57 auto child_window_server_client_id = expose_window_server_client_id(); 58 auto parent_window_id = parent_window->window_id(); 59 60 GUI::ConnectionToWindowServer::the().add_window_stealing_for_client(child_window_server_client_id, parent_window_id); 61 62 ScopeGuard guard([parent_window_id, child_window_server_client_id] { 63 GUI::ConnectionToWindowServer::the().remove_window_stealing_for_client(child_window_server_client_id, parent_window_id); 64 }); 65 66 if (path.starts_with('/')) { 67 async_request_file(id, parent_window_server_client_id, parent_window_id, path, mode); 68 } else { 69 auto full_path = LexicalPath::join(Core::DeprecatedFile::current_working_directory(), path).string(); 70 async_request_file(id, parent_window_server_client_id, parent_window_id, full_path, mode); 71 } 72 73 return handle_promise(id); 74} 75 76Result Client::open_file(GUI::Window* parent_window, DeprecatedString const& window_title, StringView path, Core::File::OpenMode requested_access) 77{ 78 auto const id = get_new_id(); 79 m_promises.set(id, PromiseAndWindow { { Core::Promise<Result>::construct() }, parent_window }); 80 81 auto parent_window_server_client_id = GUI::ConnectionToWindowServer::the().expose_client_id(); 82 auto child_window_server_client_id = expose_window_server_client_id(); 83 auto parent_window_id = parent_window->window_id(); 84 85 GUI::ConnectionToWindowServer::the().add_window_stealing_for_client(child_window_server_client_id, parent_window_id); 86 87 ScopeGuard guard([parent_window_id, child_window_server_client_id] { 88 GUI::ConnectionToWindowServer::the().remove_window_stealing_for_client(child_window_server_client_id, parent_window_id); 89 }); 90 91 async_prompt_open_file(id, parent_window_server_client_id, parent_window_id, window_title, path, requested_access); 92 93 return handle_promise(id); 94} 95 96Result Client::save_file(GUI::Window* parent_window, DeprecatedString const& name, DeprecatedString const ext, Core::File::OpenMode requested_access) 97{ 98 auto const id = get_new_id(); 99 m_promises.set(id, PromiseAndWindow { { Core::Promise<Result>::construct() }, parent_window }); 100 101 auto parent_window_server_client_id = GUI::ConnectionToWindowServer::the().expose_client_id(); 102 auto child_window_server_client_id = expose_window_server_client_id(); 103 auto parent_window_id = parent_window->window_id(); 104 105 GUI::ConnectionToWindowServer::the().add_window_stealing_for_client(child_window_server_client_id, parent_window_id); 106 107 ScopeGuard guard([parent_window_id, child_window_server_client_id] { 108 GUI::ConnectionToWindowServer::the().remove_window_stealing_for_client(child_window_server_client_id, parent_window_id); 109 }); 110 111 async_prompt_save_file(id, parent_window_server_client_id, parent_window_id, name.is_null() ? "Untitled" : name, ext.is_null() ? "txt" : ext, Core::StandardPaths::home_directory(), requested_access); 112 113 return handle_promise(id); 114} 115 116void Client::handle_prompt_end(i32 request_id, i32 error, Optional<IPC::File> const& ipc_file, Optional<DeprecatedString> const& chosen_file) 117{ 118 auto potential_data = m_promises.get(request_id); 119 VERIFY(potential_data.has_value()); 120 auto& request_data = potential_data.value(); 121 122 if (error != 0) { 123 // We don't want to show an error message for non-existent files since some applications may want 124 // to handle it as opening a new, named file. 125 if (error != -1 && error != ENOENT) 126 GUI::MessageBox::show_error(request_data.parent_window, DeprecatedString::formatted("Opening \"{}\" failed: {}", *chosen_file, strerror(error))); 127 request_data.promise->resolve(Error::from_errno(error)).release_value_but_fixme_should_propagate_errors(); 128 return; 129 } 130 131 if (Core::DeprecatedFile::is_device(ipc_file->fd())) { 132 GUI::MessageBox::show_error(request_data.parent_window, DeprecatedString::formatted("Opening \"{}\" failed: Cannot open device files", *chosen_file)); 133 request_data.promise->resolve(Error::from_string_literal("Cannot open device files")).release_value_but_fixme_should_propagate_errors(); 134 return; 135 } 136 137 if (Core::DeprecatedFile::is_directory(ipc_file->fd())) { 138 GUI::MessageBox::show_error(request_data.parent_window, DeprecatedString::formatted("Opening \"{}\" failed: Cannot open directory", *chosen_file)); 139 request_data.promise->resolve(Error::from_errno(EISDIR)).release_value_but_fixme_should_propagate_errors(); 140 return; 141 } 142 143 auto file_or_error = [&]() -> ErrorOr<File> { 144 auto stream = TRY(Core::File::adopt_fd(ipc_file->take_fd(), Core::File::OpenMode::ReadWrite)); 145 auto filename = TRY(String::from_deprecated_string(*chosen_file)); 146 return File({}, move(stream), filename); 147 }(); 148 if (file_or_error.is_error()) { 149 request_data.promise->resolve(file_or_error.release_error()).release_value_but_fixme_should_propagate_errors(); 150 return; 151 } 152 153 request_data.promise->resolve(file_or_error.release_value()).release_value_but_fixme_should_propagate_errors(); 154} 155 156void Client::die() 157{ 158 for (auto const& entry : m_promises) 159 handle_prompt_end(entry.key, ECONNRESET, {}, ""); 160} 161 162int Client::get_new_id() 163{ 164 auto const new_id = m_last_id++; 165 // Note: This verify shouldn't fail, and we should provide a valid ID 166 // But we probably have more issues if this test fails. 167 VERIFY(!m_promises.contains(new_id)); 168 return new_id; 169} 170 171Result Client::handle_promise(int id) 172{ 173 auto result = TRY(m_promises.get(id)->promise->await()); 174 m_promises.remove(id); 175 return result; 176} 177 178}