From 3c395795109a6c8aee8dd6417d245bb62893a3ce Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 14 Sep 2023 12:15:28 -0600 Subject: [PATCH] Ladybird/Android: Revive extract_tar_archive function This function used to live in AndroidPlatform.cpp, but was removed during the transition to the new app. We still need to extract the assets from the tarball that CMake creates. At least, until we come up with a generic "Resource" concept in LibCore. --- .../Android/src/main/cpp/LadybirdActivity.cpp | 157 +++++++++++++++++- Ladybird/CMakeLists.txt | 2 +- 2 files changed, 157 insertions(+), 2 deletions(-) diff --git a/Ladybird/Android/src/main/cpp/LadybirdActivity.cpp b/Ladybird/Android/src/main/cpp/LadybirdActivity.cpp index 97c499243d..997ded3838 100644 --- a/Ladybird/Android/src/main/cpp/LadybirdActivity.cpp +++ b/Ladybird/Android/src/main/cpp/LadybirdActivity.cpp @@ -5,14 +5,24 @@ */ #include "ALooperEventLoopImplementation.h" +#include #include +#include +#include #include #include +#include +#include +#include #include +#include #include +#include #include -OwnPtr s_main_event_loop; +static ErrorOr extract_tar_archive(String archive_file, DeprecatedString output_directory); + +static OwnPtr s_main_event_loop; extern "C" JNIEXPORT void JNICALL Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject /* thiz */, jstring resource_dir, jstring tag_name, jobject timer_service) @@ -27,6 +37,15 @@ Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobjec dbgln("Set resource dir to {}", s_serenity_resource_root); + auto file_or_error = Core::System::open(MUST(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root)), O_RDONLY); + if (file_or_error.is_error()) { + dbgln("No resource files, extracting assets..."); + MUST(extract_tar_archive(MUST(String::formatted("{}/ladybird-assets.tar", s_serenity_resource_root)), s_serenity_resource_root)); + } else { + dbgln("Found app-browser.png, not re-extracting assets."); + dbgln("Hopefully no developer changed the asset files and expected them to be re-extracted!"); + } + jobject timer_service_ref = env->NewGlobalRef(timer_service); JavaVM* vm = nullptr; jint ret = env->GetJavaVM(&vm); @@ -40,3 +59,139 @@ Java_org_serenityos_ladybird_LadybirdActivity_execMainEventLoop(JNIEnv*, jobject { s_main_event_loop->pump(Core::EventLoop::WaitMode::PollForEvents); } + +ErrorOr extract_tar_archive(String archive_file, DeprecatedString output_directory) +{ + constexpr size_t buffer_size = 4096; + + auto file = TRY(Core::InputBufferedFile::create(TRY(Core::File::open(archive_file, Core::File::OpenMode::Read)))); + + DeprecatedString old_pwd = TRY(Core::System::getcwd()); + + TRY(Core::System::chdir(output_directory)); + ScopeGuard go_back = [&old_pwd] { MUST(Core::System::chdir(old_pwd)); }; + + auto tar_stream = TRY(Archive::TarInputStream::construct(move(file))); + + HashMap global_overrides; + HashMap local_overrides; + + auto get_override = [&](StringView key) -> Optional { + Optional maybe_local = local_overrides.get(key); + + if (maybe_local.has_value()) + return maybe_local; + + Optional maybe_global = global_overrides.get(key); + + if (maybe_global.has_value()) + return maybe_global; + + return {}; + }; + + while (!tar_stream->finished()) { + Archive::TarFileHeader const& header = tar_stream->header(); + + // Handle meta-entries earlier to avoid consuming the file content stream. + if (header.content_is_like_extended_header()) { + switch (header.type_flag()) { + case Archive::TarFileType::GlobalExtendedHeader: { + TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) { + if (value.length() == 0) + global_overrides.remove(key); + else + global_overrides.set(key, value); + })); + break; + } + case Archive::TarFileType::ExtendedHeader: { + TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) { + local_overrides.set(key, value); + })); + break; + } + default: + warnln("Unknown extended header type '{}' of {}", (char)header.type_flag(), header.filename()); + VERIFY_NOT_REACHED(); + } + + TRY(tar_stream->advance()); + continue; + } + + Archive::TarFileStream file_stream = tar_stream->file_contents(); + + // Handle other header types that don't just have an effect on extraction. + switch (header.type_flag()) { + case Archive::TarFileType::LongName: { + StringBuilder long_name; + + Array buffer; + + while (!file_stream.is_eof()) { + auto slice = TRY(file_stream.read_some(buffer)); + long_name.append(reinterpret_cast(slice.data()), slice.size()); + } + + local_overrides.set("path", long_name.to_deprecated_string()); + TRY(tar_stream->advance()); + continue; + } + default: + // None of the relevant headers, so continue as normal. + break; + } + + LexicalPath path = LexicalPath(header.filename()); + if (!header.prefix().is_empty()) + path = path.prepend(header.prefix()); + DeprecatedString filename = get_override("path"sv).value_or(path.string()); + + DeprecatedString absolute_path = TRY(FileSystem::absolute_path(filename)).to_deprecated_string(); + auto parent_path = LexicalPath(absolute_path).parent(); + auto header_mode = TRY(header.mode()); + + switch (header.type_flag()) { + case Archive::TarFileType::NormalFile: + case Archive::TarFileType::AlternateNormalFile: { + MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); + + int fd = TRY(Core::System::open(absolute_path, O_CREAT | O_WRONLY, header_mode)); + + Array buffer; + while (!file_stream.is_eof()) { + auto slice = TRY(file_stream.read_some(buffer)); + TRY(Core::System::write(fd, slice)); + } + + TRY(Core::System::close(fd)); + break; + } + case Archive::TarFileType::SymLink: { + MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); + + TRY(Core::System::symlink(header.link_name(), absolute_path)); + break; + } + case Archive::TarFileType::Directory: { + MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); + + auto result_or_error = Core::System::mkdir(absolute_path, header_mode); + if (result_or_error.is_error() && result_or_error.error().code() != EEXIST) + return result_or_error.release_error(); + break; + } + default: + // FIXME: Implement other file types + warnln("file type '{}' of {} is not yet supported", (char)header.type_flag(), header.filename()); + VERIFY_NOT_REACHED(); + } + + // Non-global headers should be cleared after every file. + local_overrides.clear(); + + TRY(tar_stream->advance()); + } + return {}; +} diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 840e1abb3d..10aa871256 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -151,7 +151,7 @@ elseif(ANDROID) Android/src/main/cpp/ALooperEventLoopImplementation.cpp Android/src/main/cpp/TimerExecutorService.cpp ) - target_link_libraries(ladybird PRIVATE log jnigraphics android) + target_link_libraries(ladybird PRIVATE LibArchive log jnigraphics android) else() # TODO: Check for other GUI frameworks here when we move them in-tree # For now, we can export a static library of common files for chromes to link to