Skip to content

Commit

Permalink
Added progress bar ⚡
Browse files Browse the repository at this point in the history
  • Loading branch information
typio committed Mar 14, 2024
1 parent c0166ed commit 67f1399
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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*
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.{
.name = "entreepy",

.version = "1.0.0",
.version = "1.1.0",

.dependencies = .{},

Expand Down
109 changes: 76 additions & 33 deletions src/decode.zig
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
71 changes: 63 additions & 8 deletions src/encode.zig
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -22,16 +24,31 @@ 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;

for (text) |c| {
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;
Expand All @@ -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...?
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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;
}
1 change: 1 addition & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
67 changes: 67 additions & 0 deletions src/progress_bar.zig
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions src/utils.zig
Original file line number Diff line number Diff line change
@@ -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)});
}
}

1 comment on commit 67f1399

@typio
Copy link
Owner Author

@typio typio commented on 67f1399 Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closes #3

Please sign in to comment.