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