From 0d417cd604588c9e41059ce62697f62623febe47 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 2 Oct 2023 14:36:53 -0600 Subject: [PATCH] LibCore: Add Resource for platform agnostic application resource loading The first implementation is simply raw files. --- Userland/Libraries/LibCore/CMakeLists.txt | 3 + Userland/Libraries/LibCore/Forward.h | 2 + Userland/Libraries/LibCore/Resource.cpp | 88 +++++++++++++++ Userland/Libraries/LibCore/Resource.h | 90 ++++++++++++++++ .../LibCore/ResourceImplementation.cpp | 100 ++++++++++++++++++ .../LibCore/ResourceImplementation.h | 36 +++++++ .../LibCore/ResourceImplementationFile.cpp | 48 +++++++++ .../LibCore/ResourceImplementationFile.h | 26 +++++ 8 files changed, 393 insertions(+) create mode 100644 Userland/Libraries/LibCore/Resource.cpp create mode 100644 Userland/Libraries/LibCore/Resource.h create mode 100644 Userland/Libraries/LibCore/ResourceImplementation.cpp create mode 100644 Userland/Libraries/LibCore/ResourceImplementation.h create mode 100644 Userland/Libraries/LibCore/ResourceImplementationFile.cpp create mode 100644 Userland/Libraries/LibCore/ResourceImplementationFile.h diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index 18488ece838..d5b63f28052 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -21,6 +21,9 @@ set(SOURCES Notifier.cpp Process.cpp ProcessStatisticsReader.cpp + Resource.cpp + ResourceImplementation.cpp + ResourceImplementationFile.cpp SecretString.cpp SessionManagement.cpp Socket.cpp diff --git a/Userland/Libraries/LibCore/Forward.h b/Userland/Libraries/LibCore/Forward.h index acaa93f9509..a36dfad0494 100644 --- a/Userland/Libraries/LibCore/Forward.h +++ b/Userland/Libraries/LibCore/Forward.h @@ -32,6 +32,8 @@ class NetworkJob; class NetworkResponse; class Notifier; class ProcessStatisticsReader; +class Resource; +class ResourceImplementation; class Socket; template class Promise; diff --git a/Userland/Libraries/LibCore/Resource.cpp b/Userland/Libraries/LibCore/Resource.cpp new file mode 100644 index 00000000000..c86bcf3c9a4 --- /dev/null +++ b/Userland/Libraries/LibCore/Resource.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Core { + +Resource::Resource(String path, Scheme scheme, NonnullOwnPtr file) + : m_path(move(path)) + , m_scheme(scheme) + , m_data(move(file)) +{ +} + +Resource::Resource(String path, Scheme scheme, ByteBuffer buffer) + : m_path(move(path)) + , m_scheme(scheme) + , m_data(move(buffer)) +{ +} + +Resource::Resource(String path, Scheme scheme, DirectoryTag) + : m_path(move(path)) + , m_scheme(scheme) + , m_data(DirectoryTag {}) +{ +} + +ErrorOr> Resource::load_from_uri(StringView uri) +{ + return ResourceImplementation::the().load_from_uri(uri); +} + +[[nodiscard]] String Resource::uri() const +{ + return MUST(String::formatted("{}://{}", m_scheme == Scheme::Resource ? "resource"sv : "file"sv, m_path)); +} + +[[nodiscard]] Optional Resource::filesystem_path() const +{ + return ResourceImplementation::the().filesystem_path(*this); +} + +[[nodiscard]] String Resource::filename() const +{ + return MUST(String::from_utf8(LexicalPath(m_path.bytes_as_string_view()).basename())); +} + +[[nodiscard]] Vector Resource::children() const +{ + return ResourceImplementation::the().child_names(*this); +} + +[[nodiscard]] ByteBuffer Resource::clone_data() const +{ + return m_data.visit( + [](NonnullOwnPtr const& file) { return MUST(ByteBuffer::copy(file->bytes())); }, + [](ByteBuffer const& buffer) { return buffer; }, + [](DirectoryTag) -> ByteBuffer { VERIFY_NOT_REACHED(); }); +} + +[[nodiscard]] ByteBuffer Resource::release_data() && +{ + VERIFY(!m_data.has()); + + if (m_data.has>()) + return MUST(ByteBuffer::copy(m_data.get>()->bytes())); + return move(m_data).get(); +} + +[[nodiscard]] ReadonlyBytes Resource::data() const +{ + return m_data.visit( + [](NonnullOwnPtr const& file) { return file->bytes(); }, + [](ByteBuffer const& buffer) { return buffer.bytes(); }, + [](DirectoryTag) -> ReadonlyBytes { VERIFY_NOT_REACHED(); }); +} + +[[nodiscard]] FixedMemoryStream Resource::stream() const +{ + return FixedMemoryStream(data()); +} +} diff --git a/Userland/Libraries/LibCore/Resource.h b/Userland/Libraries/LibCore/Resource.h new file mode 100644 index 00000000000..a9b434b0a82 --- /dev/null +++ b/Userland/Libraries/LibCore/Resource.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Core { +class Resource : public RefCounted { +public: + static ErrorOr> load_from_uri(StringView); + + [[nodiscard]] bool is_file() const { return !m_data.has(); } + [[nodiscard]] bool is_directory() const { return m_data.has(); } + + [[nodiscard]] String uri() const; + [[nodiscard]] String filename() const; + [[nodiscard]] Optional filesystem_path() const; + + [[nodiscard]] ByteBuffer clone_data() const; + [[nodiscard]] ByteBuffer release_data() &&; + [[nodiscard]] ReadonlyBytes data() const; + [[nodiscard]] FixedMemoryStream stream() const; + + [[nodiscard]] Vector children() const; + // Depth-first + template Callback> + IterationDecision for_each_descendant(Callback&&) const; + + template Callback> + void for_each_descendant_file(Callback&&) const; + + struct DirectoryTag { }; + +private: + friend class ResourceImplementation; + + enum class Scheme { + File, + Resource, + }; + + Resource(String path, Scheme, NonnullOwnPtr); + Resource(String path, Scheme, ByteBuffer); + Resource(String path, Scheme, DirectoryTag); + + String m_path; // Relative to scheme root. File: abspath, Resource: resource root + Scheme m_scheme; + + Variant, ByteBuffer> m_data; +}; + +template Callback> +IterationDecision Resource::for_each_descendant(Callback&& callback) const +{ + auto children = this->children(); + for (auto const& child : children) { + if (auto child_resource = load_from_uri(MUST(String::formatted("{}/{}", uri(), child))); !child_resource.is_error()) { + if (callback(*child_resource.value()) == IterationDecision::Break) + return IterationDecision::Break; + if (child_resource.value()->for_each_descendant(callback) == IterationDecision::Break) + return IterationDecision::Break; + } + } + return IterationDecision::Continue; +} + +template Callback> +void Resource::for_each_descendant_file(Callback&& callback) const +{ + for_each_descendant([callback = forward(callback)](Resource const& resource) { + if (resource.is_directory()) + return IterationDecision::Continue; + return callback(resource); + }); +} + +} diff --git a/Userland/Libraries/LibCore/ResourceImplementation.cpp b/Userland/Libraries/LibCore/ResourceImplementation.cpp new file mode 100644 index 00000000000..edbda987136 --- /dev/null +++ b/Userland/Libraries/LibCore/ResourceImplementation.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Core { + +static OwnPtr s_the; + +void ResourceImplementation::install(OwnPtr the) +{ + s_the = move(the); +} + +ResourceImplementation& ResourceImplementation::the() +{ + if (!s_the) + install(make("/res"_string)); + return *s_the; +} + +NonnullRefPtr ResourceImplementation::make_resource(String full_path, NonnullOwnPtr file) +{ + return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, move(file))); +} + +NonnullRefPtr ResourceImplementation::make_resource(String full_path, ByteBuffer buffer) +{ + return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, move(buffer))); +} + +NonnullRefPtr ResourceImplementation::make_directory_resource(String full_path) +{ + return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, Resource::DirectoryTag {})); +} + +ErrorOr> ResourceImplementation::load_from_uri(StringView uri) +{ + StringView const file_scheme = "file://"sv; + StringView const resource_scheme = "resource://"sv; + + if (uri.starts_with(resource_scheme)) + return load_from_resource_scheme_uri(uri); + + if (uri.starts_with(file_scheme)) { + auto path = uri.substring_view(file_scheme.length()); + if (is_directory(path)) + return adopt_ref(*new Resource(TRY(String::from_utf8(path)), Resource::Scheme::File, Resource::DirectoryTag {})); + return adopt_ref(*new Resource(TRY(String::from_utf8(path)), Resource::Scheme::File, TRY(MappedFile::map(path)))); + } + + dbgln("ResourceImplementation: Unknown scheme for {}", uri); + return Error::from_string_view("Invalid scheme"sv); +} + +Vector ResourceImplementation::child_names(Resource const& resource) +{ + if (!resource.is_directory()) + return {}; + + if (resource.m_scheme == Resource::Scheme::Resource) + return child_names_for_resource_scheme(resource); + + VERIFY(resource.m_scheme == Resource::Scheme::File); + + Vector children; + Core::DirIterator it(resource.filesystem_path().release_value().to_deprecated_string(), Core::DirIterator::SkipParentAndBaseDir); + while (it.has_next()) + children.append(MUST(String::from_deprecated_string(it.next_path()))); + + return children; +} + +Optional ResourceImplementation::filesystem_path(Resource const& resource) +{ + if (resource.m_scheme == Resource::Scheme::Resource) + return filesystem_path_for_resource_scheme(resource.m_path); + + VERIFY(resource.m_scheme == Resource::Scheme::File); + + return resource.m_path; +} + +// Note: This is a copy of the impl in LibFilesystem, but we can't link that to LibCore +bool ResourceImplementation::is_directory(StringView filesystem_path) +{ + auto st_or_error = System::stat(filesystem_path); + if (st_or_error.is_error()) + return false; + auto st = st_or_error.release_value(); + return S_ISDIR(st.st_mode); +} + +} diff --git a/Userland/Libraries/LibCore/ResourceImplementation.h b/Userland/Libraries/LibCore/ResourceImplementation.h new file mode 100644 index 00000000000..29df9581635 --- /dev/null +++ b/Userland/Libraries/LibCore/ResourceImplementation.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Core { +class ResourceImplementation { +public: + ErrorOr> load_from_uri(StringView); + Vector child_names(Resource const&); + Optional filesystem_path(Resource const&); + + virtual ~ResourceImplementation() = default; + + static void install(OwnPtr); + static ResourceImplementation& the(); + +protected: + virtual ErrorOr> load_from_resource_scheme_uri(StringView) = 0; + virtual Vector child_names_for_resource_scheme(Resource const&) = 0; + virtual Optional filesystem_path_for_resource_scheme(String const&) = 0; + + static bool is_directory(StringView filesystem_path); + + static NonnullRefPtr make_resource(String full_path, NonnullOwnPtr); + static NonnullRefPtr make_resource(String full_path, ByteBuffer); + static NonnullRefPtr make_directory_resource(String full_path); +}; +} diff --git a/Userland/Libraries/LibCore/ResourceImplementationFile.cpp b/Userland/Libraries/LibCore/ResourceImplementationFile.cpp new file mode 100644 index 00000000000..6b2a7d6cff8 --- /dev/null +++ b/Userland/Libraries/LibCore/ResourceImplementationFile.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Core { +ResourceImplementationFile::ResourceImplementationFile(String base_directory) + : m_base_directory(move(base_directory)) +{ +} + +ErrorOr> ResourceImplementationFile::load_from_resource_scheme_uri(StringView uri) +{ + StringView const resource_scheme = "resource://"sv; + + VERIFY(uri.starts_with(resource_scheme)); + + auto path = TRY(String::from_utf8(uri.substring_view(resource_scheme.length()))); + auto full_path = TRY(String::from_deprecated_string(LexicalPath::join(m_base_directory, path).string())); + if (is_directory(full_path)) + return make_directory_resource(move(path)); + + return make_resource(path, TRY(MappedFile::map(full_path))); +} + +Vector ResourceImplementationFile::child_names_for_resource_scheme(Resource const& resource) +{ + Vector children; + Core::DirIterator it(resource.filesystem_path().release_value().to_deprecated_string(), Core::DirIterator::SkipParentAndBaseDir); + while (it.has_next()) + children.append(MUST(String::from_deprecated_string(it.next_path()))); + + return children; +} + +Optional ResourceImplementationFile::filesystem_path_for_resource_scheme(String const& relative_path) +{ + return MUST(String::from_deprecated_string(LexicalPath::join(m_base_directory, relative_path).string())); +} + +} diff --git a/Userland/Libraries/LibCore/ResourceImplementationFile.h b/Userland/Libraries/LibCore/ResourceImplementationFile.h new file mode 100644 index 00000000000..f671a9c7a3c --- /dev/null +++ b/Userland/Libraries/LibCore/ResourceImplementationFile.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Core { +class ResourceImplementationFile : public ResourceImplementation { +public: + explicit ResourceImplementationFile(String base_directory); + + virtual ErrorOr> load_from_resource_scheme_uri(StringView) override; + virtual Vector child_names_for_resource_scheme(Resource const&) override; + virtual Optional filesystem_path_for_resource_scheme(String const&) override; + +private: + String m_base_directory; +}; +}