LibCore: Add Resource for platform agnostic application resource loading

The first implementation is simply raw files.
This commit is contained in:
Andrew Kaster 2023-10-02 14:36:53 -06:00 committed by Andrew Kaster
parent f4a89c31c6
commit 0d417cd604
Notes: sideshowbarker 2024-07-17 09:49:33 +09:00
8 changed files with 393 additions and 0 deletions

View File

@ -21,6 +21,9 @@ set(SOURCES
Notifier.cpp
Process.cpp
ProcessStatisticsReader.cpp
Resource.cpp
ResourceImplementation.cpp
ResourceImplementationFile.cpp
SecretString.cpp
SessionManagement.cpp
Socket.cpp

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -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);
};
}

View File

@ -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()));
}
}

View File

@ -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;
};
}