瀏覽代碼

LibCore: Add Resource for platform agnostic application resource loading

The first implementation is simply raw files.
Andrew Kaster 1 年之前
父節點
當前提交
0d417cd604

+ 3 - 0
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

+ 2 - 0
Userland/Libraries/LibCore/Forward.h

@@ -32,6 +32,8 @@ class NetworkJob;
 class NetworkResponse;
 class Notifier;
 class ProcessStatisticsReader;
+class Resource;
+class ResourceImplementation;
 class Socket;
 template<typename Result, typename TError = AK::Error>
 class Promise;

+ 88 - 0
Userland/Libraries/LibCore/Resource.cpp

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/LexicalPath.h>
+#include <LibCore/Resource.h>
+#include <LibCore/ResourceImplementation.h>
+
+namespace Core {
+
+Resource::Resource(String path, Scheme scheme, NonnullOwnPtr<Core::MappedFile> 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<NonnullRefPtr<Resource>> 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<String> 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<String> Resource::children() const
+{
+    return ResourceImplementation::the().child_names(*this);
+}
+
+[[nodiscard]] ByteBuffer Resource::clone_data() const
+{
+    return m_data.visit(
+        [](NonnullOwnPtr<Core::MappedFile> 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<DirectoryTag>());
+
+    if (m_data.has<NonnullOwnPtr<Core::MappedFile>>())
+        return MUST(ByteBuffer::copy(m_data.get<NonnullOwnPtr<Core::MappedFile>>()->bytes()));
+    return move(m_data).get<ByteBuffer>();
+}
+
+[[nodiscard]] ReadonlyBytes Resource::data() const
+{
+    return m_data.visit(
+        [](NonnullOwnPtr<Core::MappedFile> const& file) { return file->bytes(); },
+        [](ByteBuffer const& buffer) { return buffer.bytes(); },
+        [](DirectoryTag) -> ReadonlyBytes { VERIFY_NOT_REACHED(); });
+}
+
+[[nodiscard]] FixedMemoryStream Resource::stream() const
+{
+    return FixedMemoryStream(data());
+}
+}

+ 90 - 0
Userland/Libraries/LibCore/Resource.h

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/Error.h>
+#include <AK/MemoryStream.h>
+#include <AK/RefPtr.h>
+#include <AK/Span.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/Variant.h>
+#include <LibCore/File.h>
+#include <LibCore/MappedFile.h>
+
+namespace Core {
+class Resource : public RefCounted<Resource> {
+public:
+    static ErrorOr<NonnullRefPtr<Resource>> load_from_uri(StringView);
+
+    [[nodiscard]] bool is_file() const { return !m_data.has<DirectoryTag>(); }
+    [[nodiscard]] bool is_directory() const { return m_data.has<DirectoryTag>(); }
+
+    [[nodiscard]] String uri() const;
+    [[nodiscard]] String filename() const;
+    [[nodiscard]] Optional<String> filesystem_path() const;
+
+    [[nodiscard]] ByteBuffer clone_data() const;
+    [[nodiscard]] ByteBuffer release_data() &&;
+    [[nodiscard]] ReadonlyBytes data() const;
+    [[nodiscard]] FixedMemoryStream stream() const;
+
+    [[nodiscard]] Vector<String> children() const;
+    // Depth-first
+    template<IteratorFunction<Resource const&> Callback>
+    IterationDecision for_each_descendant(Callback&&) const;
+
+    template<IteratorFunction<Resource const&> Callback>
+    void for_each_descendant_file(Callback&&) const;
+
+    struct DirectoryTag { };
+
+private:
+    friend class ResourceImplementation;
+
+    enum class Scheme {
+        File,
+        Resource,
+    };
+
+    Resource(String path, Scheme, NonnullOwnPtr<Core::MappedFile>);
+    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<DirectoryTag, NonnullOwnPtr<Core::MappedFile>, ByteBuffer> m_data;
+};
+
+template<IteratorFunction<Resource const&> 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<IteratorFunction<Resource const&> Callback>
+void Resource::for_each_descendant_file(Callback&& callback) const
+{
+    for_each_descendant([callback = forward<Callback>(callback)](Resource const& resource) {
+        if (resource.is_directory())
+            return IterationDecision::Continue;
+        return callback(resource);
+    });
+}
+
+}

+ 100 - 0
Userland/Libraries/LibCore/ResourceImplementation.cpp

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/DirIterator.h>
+#include <LibCore/ResourceImplementation.h>
+#include <LibCore/ResourceImplementationFile.h>
+#include <LibCore/System.h>
+
+namespace Core {
+
+static OwnPtr<ResourceImplementation> s_the;
+
+void ResourceImplementation::install(OwnPtr<ResourceImplementation> the)
+{
+    s_the = move(the);
+}
+
+ResourceImplementation& ResourceImplementation::the()
+{
+    if (!s_the)
+        install(make<ResourceImplementationFile>("/res"_string));
+    return *s_the;
+}
+
+NonnullRefPtr<Resource> ResourceImplementation::make_resource(String full_path, NonnullOwnPtr<Core::MappedFile> file)
+{
+    return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, move(file)));
+}
+
+NonnullRefPtr<Resource> ResourceImplementation::make_resource(String full_path, ByteBuffer buffer)
+{
+    return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, move(buffer)));
+}
+
+NonnullRefPtr<Resource> ResourceImplementation::make_directory_resource(String full_path)
+{
+    return adopt_ref(*new Resource(move(full_path), Resource::Scheme::Resource, Resource::DirectoryTag {}));
+}
+
+ErrorOr<NonnullRefPtr<Resource>> 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<String> 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<String> 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<String> 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);
+}
+
+}

+ 36 - 0
Userland/Libraries/LibCore/ResourceImplementation.h

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <AK/StringView.h>
+#include <LibCore/Resource.h>
+
+namespace Core {
+class ResourceImplementation {
+public:
+    ErrorOr<NonnullRefPtr<Resource>> load_from_uri(StringView);
+    Vector<String> child_names(Resource const&);
+    Optional<String> filesystem_path(Resource const&);
+
+    virtual ~ResourceImplementation() = default;
+
+    static void install(OwnPtr<ResourceImplementation>);
+    static ResourceImplementation& the();
+
+protected:
+    virtual ErrorOr<NonnullRefPtr<Resource>> load_from_resource_scheme_uri(StringView) = 0;
+    virtual Vector<String> child_names_for_resource_scheme(Resource const&) = 0;
+    virtual Optional<String> filesystem_path_for_resource_scheme(String const&) = 0;
+
+    static bool is_directory(StringView filesystem_path);
+
+    static NonnullRefPtr<Resource> make_resource(String full_path, NonnullOwnPtr<Core::MappedFile>);
+    static NonnullRefPtr<Resource> make_resource(String full_path, ByteBuffer);
+    static NonnullRefPtr<Resource> make_directory_resource(String full_path);
+};
+}

+ 48 - 0
Userland/Libraries/LibCore/ResourceImplementationFile.cpp

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/LexicalPath.h>
+#include <AK/StringView.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/Resource.h>
+#include <LibCore/ResourceImplementationFile.h>
+
+namespace Core {
+ResourceImplementationFile::ResourceImplementationFile(String base_directory)
+    : m_base_directory(move(base_directory))
+{
+}
+
+ErrorOr<NonnullRefPtr<Resource>> 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<String> ResourceImplementationFile::child_names_for_resource_scheme(Resource const& resource)
+{
+    Vector<String> 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<String> ResourceImplementationFile::filesystem_path_for_resource_scheme(String const& relative_path)
+{
+    return MUST(String::from_deprecated_string(LexicalPath::join(m_base_directory, relative_path).string()));
+}
+
+}

+ 26 - 0
Userland/Libraries/LibCore/ResourceImplementationFile.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <AK/StringView.h>
+#include <LibCore/Resource.h>
+#include <LibCore/ResourceImplementation.h>
+
+namespace Core {
+class ResourceImplementationFile : public ResourceImplementation {
+public:
+    explicit ResourceImplementationFile(String base_directory);
+
+    virtual ErrorOr<NonnullRefPtr<Resource>> load_from_resource_scheme_uri(StringView) override;
+    virtual Vector<String> child_names_for_resource_scheme(Resource const&) override;
+    virtual Optional<String> filesystem_path_for_resource_scheme(String const&) override;
+
+private:
+    String m_base_directory;
+};
+}