summaryrefslogtreecommitdiff
path: root/Userland/Utilities
diff options
context:
space:
mode:
authorIdan Horowitz <idan.horowitz@gmail.com>2021-03-22 23:13:27 +0200
committerAndreas Kling <kling@serenityos.org>2021-03-23 16:09:36 +0100
commit97216c935a6a008d726c3cefb433d7d7ab8ad7d2 (patch)
treef2b7dd1656b6dcf700d65e9f062a366e09b969e2 /Userland/Utilities
parent550ae23e8018bbde91b00af59071a3aee71cd78b (diff)
downloadserenity-97216c935a6a008d726c3cefb433d7d7ab8ad7d2.zip
Userland: Add simple zip utility
This uses the recently added ZipOutputStream in LibArchive.
Diffstat (limited to 'Userland/Utilities')
-rw-r--r--Userland/Utilities/CMakeLists.txt1
-rw-r--r--Userland/Utilities/zip.cpp138
2 files changed, 139 insertions, 0 deletions
diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt
index 34b675b690..9f82aaf3b4 100644
--- a/Userland/Utilities/CMakeLists.txt
+++ b/Userland/Utilities/CMakeLists.txt
@@ -48,6 +48,7 @@ target_link_libraries(test-pthread LibThread)
target_link_libraries(test-web LibWeb)
target_link_libraries(tt LibPthread)
target_link_libraries(grep LibRegex)
+target_link_libraries(zip LibArchive LibCompress LibCrypto)
target_link_libraries(unzip LibArchive LibCompress)
target_link_libraries(gunzip LibCompress)
target_link_libraries(CppParserTest LibCpp LibGUI)
diff --git a/Userland/Utilities/zip.cpp b/Userland/Utilities/zip.cpp
new file mode 100644
index 0000000000..47618a4c2c
--- /dev/null
+++ b/Userland/Utilities/zip.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <LibArchive/Zip.h>
+#include <LibCompress/Deflate.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/File.h>
+#include <LibCore/FileStream.h>
+#include <LibCrypto/Checksum/CRC32.h>
+
+int main(int argc, char** argv)
+{
+ const char* zip_path;
+ Vector<const char*> source_paths;
+ bool recurse = false;
+ bool force = false;
+
+ Core::ArgsParser args_parser;
+ args_parser.add_positional_argument(zip_path, "Zip file path", "zipfile", Core::ArgsParser::Required::Yes);
+ args_parser.add_positional_argument(source_paths, "Input files to be archived", "files", Core::ArgsParser::Required::Yes);
+ args_parser.add_option(recurse, "Travel the directory structure recursively", "recurse-paths", 'r');
+ args_parser.add_option(force, "Overwrite existing zip file", "force", 'f');
+ args_parser.parse(argc, argv);
+
+ String zip_file_path { zip_path };
+ if (Core::File::exists(zip_file_path)) {
+ if (force) {
+ outln("{} already exists, overwriting...", zip_file_path);
+ } else {
+ warnln("{} already exists, aborting!", zip_file_path);
+ return 1;
+ }
+ }
+
+ auto file_stream_or_error = Core::OutputFileStream::open(zip_file_path);
+ if (file_stream_or_error.is_error()) {
+ warnln("Failed to open zip file: {}", file_stream_or_error.error());
+ return 1;
+ }
+
+ outln("Archive: {}", zip_file_path);
+
+ auto file_stream = file_stream_or_error.value();
+ Archive::ZipOutputStream zip_stream { file_stream };
+
+ auto add_file = [&](String path) {
+ auto file = Core::File::construct(path);
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ warnln("Failed to open {}: {}", path, file->error_string());
+ return;
+ }
+
+ auto canonicalized_path = LexicalPath::canonicalized_path(path);
+ auto file_buffer = file->read_all();
+ Archive::ZipMember member {};
+ member.name = canonicalized_path;
+
+ auto deflate_buffer = Compress::DeflateCompressor::compress_all(file_buffer);
+ if (deflate_buffer.has_value() && deflate_buffer.value().size() < file_buffer.size()) {
+ member.compressed_data = deflate_buffer.value().bytes();
+ member.compression_method = Archive::ZipCompressionMethod::Deflate;
+ auto compression_ratio = (double)deflate_buffer.value().size() / file_buffer.size();
+ outln(" adding: {} (deflated {}%)", canonicalized_path, (int)(compression_ratio * 100));
+ } else {
+ member.compressed_data = file_buffer.bytes();
+ member.compression_method = Archive::ZipCompressionMethod::Store;
+ outln(" adding: {} (stored 0%)", canonicalized_path);
+ }
+ member.uncompressed_size = file_buffer.size();
+ Crypto::Checksum::CRC32 checksum { file_buffer.bytes() };
+ member.crc32 = checksum.digest();
+ member.is_directory = false;
+ zip_stream.add_member(member);
+ };
+
+ auto add_directory = [&](String path, auto handle_directory) -> void {
+ auto canonicalized_path = String::formatted("{}/", LexicalPath::canonicalized_path(path));
+ Archive::ZipMember member {};
+ member.name = canonicalized_path;
+ member.compressed_data = {};
+ member.compression_method = Archive::ZipCompressionMethod::Store;
+ member.uncompressed_size = 0;
+ member.crc32 = 0;
+ member.is_directory = true;
+ zip_stream.add_member(member);
+ outln(" adding: {} (stored 0%)", canonicalized_path);
+
+ if (!recurse)
+ return;
+
+ Core::DirIterator it(path, Core::DirIterator::Flags::SkipParentAndBaseDir);
+ while (it.has_next()) {
+ auto child_path = it.next_full_path();
+ if (!Core::File::is_directory(child_path)) {
+ add_file(child_path);
+ } else {
+ handle_directory(child_path, handle_directory);
+ }
+ }
+ };
+
+ for (const String& source_path : source_paths) {
+ if (Core::File::is_directory(source_path)) {
+ add_directory(source_path, add_directory);
+ } else {
+ add_file(source_path);
+ }
+ }
+
+ zip_stream.finish();
+
+ return 0;
+}