From 67f13997250498341f870121122d55645105ddc0 Mon Sep 17 00:00:00 2001 From: Thomas Huber Date: Thu, 14 Mar 2024 03:04:09 -0700 Subject: [PATCH] Added progress bar :zap: --- .github/workflows/release.yml | 2 +- build.zig.zon | 2 +- src/decode.zig | 109 ++++++++++++++++++++++++---------- src/encode.zig | 71 +++++++++++++++++++--- src/main.zig | 1 + src/progress_bar.zig | 67 +++++++++++++++++++++ src/utils.zig | 13 ++++ 7 files changed, 222 insertions(+), 43 deletions(-) create mode 100644 src/progress_bar.zig create mode 100644 src/utils.zig diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90736ed..74816a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,4 +77,4 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create v1.0.0 -t "1.0.0" entreepy/entreepy* + gh release create v1.1.0 -t "1.1.0" entreepy/entreepy* diff --git a/build.zig.zon b/build.zig.zon index 54dc44d..38fbd8e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,7 +1,7 @@ .{ .name = "entreepy", - .version = "1.0.0", + .version = "1.1.0", .dependencies = .{}, diff --git a/src/decode.zig b/src/decode.zig index 79637ed..0930e47 100644 --- a/src/decode.zig +++ b/src/decode.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const print_progress = @import("progress_bar.zig").print_progress; +const format_file_size = @import("utils.zig").format_file_size; pub const DecodeFlags = struct { write_output: bool = false, @@ -11,9 +13,20 @@ pub const DecodeFlags = struct { pub fn decode(allocator: Allocator, compressed_text: []const u8, out_writer: anytype, std_out: std.fs.File, flags: DecodeFlags) !usize { var bytes_written: u32 = 0; const start_time = std.time.microTimestamp(); - defer if (flags.debug) std_out.writer().print("\ntime taken: {d}μs\n", .{std.time.microTimestamp() - + defer if (flags.debug) std_out.writer().print("time taken: {d}μs\n", .{std.time.microTimestamp() - start_time}) catch {}; + var decoding_progress: usize = 0; + var decoding_state_msg: []const u8 = "Decoding file..."; + + var print_progress_thread: ?std.Thread = null; + if (!flags.print_output) { + print_progress_thread = try std.Thread.spawn(.{}, print_progress, .{ 1, &decoding_progress, &decoding_state_msg }); + } + + decoding_progress = 5; + decoding_state_msg = "Reading file header..."; + var reading_dict_letter: bool = true; var reading_dict_code_len: bool = false; var reading_dict_code: bool = false; @@ -42,6 +55,9 @@ pub fn decode(allocator: Allocator, compressed_text: []const u8, out_writer: any var current_code_length: u8 = 0; var current_code_data: usize = 0; + decoding_progress = 10; + decoding_state_msg = "Reading prefix code dictionary..."; + var global_pos: usize = 0; var pos: usize = 0; // bit pos in byte var build_bits: usize = 0b0; @@ -130,48 +146,75 @@ pub fn decode(allocator: Allocator, compressed_text: []const u8, out_writer: any var testing_code: usize = 0; var decoded_letters_read: usize = 0; - for (compressed_text[5 + global_pos ..]) |byte| { - window <<= 8; - window |= byte; - window_len += 8; - - // while there are potential matches in window - decode_text: while (window_len >= longest_code) { - // loop through all possible code lengths, checking start of window for match - checking_code_len = shortest_code; - while (window_len >= checking_code_len) { - if (decoded_letters_read >= decode_body_length or - window_len < checking_code_len) - { - break :decode_text; - } + decoding_progress = 20; + decoding_state_msg = "Decoding text..."; + + const decoding_sections = 30; + for (0..decoding_sections) |s| { + decoding_progress = 30 + (100 - 30) * s / decoding_sections; + + const body_start = 5 + global_pos; + const body_length = compressed_text.len - body_start; + + for (compressed_text[body_start + s * body_length / decoding_sections .. body_start + (s + 1) * body_length / decoding_sections]) |byte| { + // for (compressed_text[5 + global_pos ..]) |byte| { + window <<= 8; + window |= byte; + window_len += 8; + + // while there are potential matches in window + decode_text: while (window_len >= longest_code) { + // loop through all possible code lengths, checking start of window for match + checking_code_len = shortest_code; + while (window_len >= checking_code_len) { + if (decoded_letters_read >= decode_body_length or + window_len < checking_code_len) + { + break :decode_text; + } - testing_code = window & - ((@as(u32, 0b1) << @as(u5, @truncate(checking_code_len))) - 1) << @as(u5, @truncate(window_len - checking_code_len)); + testing_code = window & + ((@as(u32, 0b1) << @as(u5, @truncate(checking_code_len))) - 1) << @as(u5, @truncate(window_len - checking_code_len)); - testing_code >>= @as(u6, @truncate(window_len - checking_code_len)); + testing_code >>= @as(u6, @truncate(window_len - checking_code_len)); - if (decode_table.get(testing_code)) |entry| { - if (entry[checking_code_len - 1] > 0) { - const c = entry[checking_code_len - 1]; + if (decode_table.get(testing_code)) |entry| { + if (entry[checking_code_len - 1] > 0) { + const c = entry[checking_code_len - 1]; - if (flags.write_output) { - try out_writer.writeByte(c); - bytes_written += 1; - } - if (flags.print_output) try std_out.writer().print("{c}", .{c}); + if (flags.write_output) { + try out_writer.writeByte(c); + bytes_written += 1; + } + if (flags.print_output) try std_out.writer().print("{c}", .{c}); - decoded_letters_read += 1; + decoded_letters_read += 1; - window = window & ((@as(u32, 0b1) << - @as(u5, @truncate(window_len - checking_code_len))) - 1); - window_len -= checking_code_len; - checking_code_len = shortest_code; + window = window & ((@as(u32, 0b1) << + @as(u5, @truncate(window_len - checking_code_len))) - 1); + window_len -= checking_code_len; + checking_code_len = shortest_code; + } } + checking_code_len += 1; } - checking_code_len += 1; } } } + + if (!flags.print_output) { + decoding_progress = 100; + decoding_state_msg = "Done decompressing!"; + print_progress_thread.?.join(); + } + + const formatted_original_size = format_file_size(allocator, @floatFromInt(compressed_text.len)) catch unreachable; + defer allocator.free(formatted_original_size); + + const formatted_decompressed_size = format_file_size(allocator, @floatFromInt(bytes_written)) catch unreachable; + defer allocator.free(formatted_decompressed_size); + + std.debug.print("{s} => {s}\n", .{ formatted_original_size, formatted_decompressed_size }); + return bytes_written; } diff --git a/src/encode.zig b/src/encode.zig index c19df25..445362b 100644 --- a/src/encode.zig +++ b/src/encode.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Queue = @import("queue.zig").Queue; +const print_progress = @import("progress_bar.zig").print_progress; +const format_file_size = @import("utils.zig").format_file_size; const Allocator = std.mem.Allocator; @@ -22,9 +24,21 @@ const Node = struct { pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_out: std.fs.File, flags: EncodeFlags) !usize { const start_time = std.time.microTimestamp(); - defer if (flags.debug) std_out.writer().print("\ntime taken: {d}μs\n", .{std.time.microTimestamp() - + defer if (flags.debug) std_out.writer().print("time taken: {d}μs\n", .{std.time.microTimestamp() - start_time}) catch {}; + var encoding_progress: usize = 0; + var encoding_state_msg: []const u8 = "Encoding file..."; + + var print_progress_thread: ?std.Thread = null; + // TODO: Make progress bar work with debug flag + if (!flags.print_output and !flags.debug) { + print_progress_thread = try std.Thread.spawn(.{}, print_progress, .{ 0, &encoding_progress, &encoding_state_msg }); + } + + encoding_progress = 10; + encoding_state_msg = "Counting characters..."; + // array where index is the ascii char and value is number of occurences var occurences_book = [_]usize{0} ** 256; @@ -32,6 +46,9 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o occurences_book[c] += 1; } + encoding_progress = 20; + encoding_state_msg = "Sorting characters..."; + // an array of ascii chars sorted from least to most frequent then // alphabetically, 0 occurence ascii chars at the end var sorted_letter_book = [_]u8{0} ** 256; @@ -56,6 +73,9 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o min_value = next_min_value; } + encoding_progress = 25; + encoding_state_msg = "Building code tree..."; + const symbols_length = book_index; // exclusive index // max amt of nodes is 256 leaves + 257 internal? not for huffman...? @@ -130,6 +150,14 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o path: Code, }; + encoding_progress = 30; + encoding_state_msg = "Creating codes..."; + + // clear progress bar if it's being printed ahead of printing codes + // if (flags.debug and !flags.print_output) { + // try std_out.writer().print("\x1B[4F\x1B[4K", .{}); + // } + var traversal_stack: [513]?HistoryNode = [_]?HistoryNode{null} ** 513; var traversal_stack_top: usize = 1; traversal_stack[0] = HistoryNode{ @@ -185,6 +213,10 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o } } + // if (flags.debug and !flags.print_output) { + // try std_out.writer().print("\n\n", .{}); + // } + // debug check that there are no colliding prefixes if (flags.debug) { for (dictionary, 0..) |code_1, i| { @@ -214,6 +246,9 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o } } + encoding_progress = 35; + encoding_state_msg = "Writing file header..."; + // estimate of header length when every unique char is used const max_header_length: usize = 7200; var out_buffer = try allocator.alloc(u8, max_header_length + text.len); @@ -263,13 +298,19 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o try bit_stream_writer.flushBits(); bits_written = if (bits_written % 8 != 0) (bits_written / 8 + 1) * 8 else bits_written; - // write compressed bits - for (text) |char| { - const code = dictionary[char]; - var j: usize = code.length; - while (j > 0) : (j -= 1) { - try bit_stream_writer.writeBits((code.data >> @as(u5, @truncate(j - 1))) & 1, 1); - bits_written += 1; + encoding_progress = 40; + encoding_state_msg = "Writing compressed text..."; + const writing_sections = 10; + for (0..writing_sections) |i| { + encoding_progress = 60 + (100 - 60) * i / writing_sections; + // write compressed bits + for (text[i * text.len / writing_sections .. (i + 1) * text.len / writing_sections]) |char| { + const code = dictionary[char]; + var j: usize = code.length; + while (j > 0) : (j -= 1) { + try bit_stream_writer.writeBits((code.data >> @as(u5, @truncate(j - 1))) & 1, 1); + bits_written += 1; + } } } @@ -278,5 +319,19 @@ pub fn encode(allocator: Allocator, text: []const u8, out_writer: anytype, std_o if (flags.write_output) try out_writer.writeAll(out_buffer[0 .. bits_written / 8]); if (flags.debug) try std_out.writer().print("\nbits in output: {d}\n", .{bits_written}); + if (!flags.print_output and !flags.debug) { + encoding_progress = 100; + encoding_state_msg = "Done compressing!"; + print_progress_thread.?.join(); + } + + const formatted_original_size = format_file_size(allocator, @floatFromInt(text.len)) catch unreachable; + defer allocator.free(formatted_original_size); + + const formatted_compressed_size = format_file_size(allocator, @floatFromInt(bits_written / 8)) catch unreachable; + defer allocator.free(formatted_compressed_size); + + std.debug.print("{s} => {s}\n", .{ formatted_original_size, formatted_compressed_size }); + return bits_written / 8; } diff --git a/src/main.zig b/src/main.zig index 3760063..8e6f3cd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -151,6 +151,7 @@ fn run_cli(allocator: Allocator, std_out: std.fs.File) !Options { return options; } + // FIX: Generating default filename causes segfault on linux if (options.file_out_path.len == 0) { if (options.mode == .Compress) { options.file_out_path = diff --git a/src/progress_bar.zig b/src/progress_bar.zig new file mode 100644 index 0000000..3d57067 --- /dev/null +++ b/src/progress_bar.zig @@ -0,0 +1,67 @@ +const std = @import("std"); + +const Color = struct { r: i32, g: i32, b: i32 }; + +const stdout = std.io.getStdOut().writer(); +const bar_length: usize = 30; +const steps_per_color: i32 = 60; + +pub fn print_progress(theme: u8, progress: *usize, state_msg: *const []const u8) !void { + var stops: [4]Color = undefined; + switch (theme) { + 0 => { + stops = [_]Color{ Color{ .r = 0x00, .g = 0xb4, .b = 0xd8 }, Color{ .r = 0x90, .g = 0xe0, .b = 0xef }, Color{ .r = 0xca, .g = 0xc0, .b = 0xf8 }, Color{ .r = 0x90, .g = 0xe0, .b = 0xef } }; + }, + else => { + stops = [_]Color{ Color{ .r = 0x83, .g = 0x3a, .b = 0xb4 }, Color{ .r = 0xe7, .g = 0x22, .b = 0x38 }, Color{ .r = 0xfc, .g = 0xb0, .b = 0x45 }, Color{ .r = 0xe7, .g = 0x22, .b = 0x38 } }; + }, + } + + var step: usize = 0; + + std.debug.print("\n\n\n\n", .{}); + + while (progress.* <= 100) : (step += 1) { + const bar_done = progress.* * bar_length / 100; + + std.debug.print("\x1B[4F\x1B[4K", .{}); + std.debug.print("{s}\t\t\t\t\t\t\n", .{state_msg.*}); + std.debug.print("╔", .{}); + inline for (0..bar_length + 2) |_| { + std.debug.print("═", .{}); + } + + std.debug.print("╗\n║ ", .{}); + + for (0..bar_done) |j| { + const stop = stops[@divTrunc(step + j, steps_per_color) % 3]; + const stop_next = stops[(@divTrunc(step + j, steps_per_color) + 1) % 3]; + + var c: Color = undefined; + + c = Color{ + .r = stop.r + @divTrunc((stop_next.r - stop.r) * @rem(@as(i32, @intCast(step + j)), steps_per_color), steps_per_color), + .g = stop.g + @divTrunc((stop_next.g - stop.g) * @rem(@as(i32, @intCast(step + j)), steps_per_color), steps_per_color), + .b = stop.b + @divTrunc((stop_next.b - stop.b) * @rem(@as(i32, @intCast(step + j)), steps_per_color), steps_per_color), + }; + std.debug.print("\x1B[38;2;{};{};{}m█\x1B[m", .{ c.r, c.g, c.b }); + } + + for (bar_done..@max(bar_done, bar_length)) |_| { + std.debug.print(" ", .{}); + } + + std.debug.print(" ║\n╚", .{}); + + inline for (0..bar_length + 2) |_| { + std.debug.print("═", .{}); + } + + std.debug.print("╝\n", .{}); + + if (bar_done == bar_length) { + break; + } + std.time.sleep(10_000_000); + } +} diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..5b55f5e --- /dev/null +++ b/src/utils.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +pub fn format_file_size(allocator: std.mem.Allocator, byte_count: f32) ![]const u8 { + if (byte_count < 1024) { + return std.fmt.allocPrint(allocator, "{d} B", .{byte_count}); + } else if (byte_count < 1024 * 1024) { + return std.fmt.allocPrint(allocator, "{d:.2} KB", .{byte_count / 1024}); + } else if (byte_count < 1024 * 1024 * 1024) { + return std.fmt.allocPrint(allocator, "{d:.2} MB", .{byte_count / (1024 * 1024)}); + } else { + return std.fmt.allocPrint(allocator, "{d:.2} GB", .{byte_count / (1024 * 1024 * 1024)}); + } +}