From 7300983e9057376838acd006771109830f78afdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20Niel=C3=A4nder?= Date: Tue, 26 Nov 2024 23:16:14 +0100 Subject: [PATCH] feat: improved std.progress implementation --- src/command.zig | 12 +++---- src/install.zig | 76 +++++++++++++++++++++++++++----------------- src/main.zig | 8 ++++- src/util/extract.zig | 25 +++++++++++---- src/util/http.zig | 67 +++++++++++++++++++++++--------------- 5 files changed, 118 insertions(+), 70 deletions(-) diff --git a/src/command.zig b/src/command.zig index 5cd70aa..f1a8984 100644 --- a/src/command.zig +++ b/src/command.zig @@ -49,7 +49,7 @@ const command_opts = [_]CommandOption{ }; /// Parse and handle commands -pub fn handle_command(params: []const []const u8) !void { +pub fn handle_command(params: []const []const u8, root_node: std.Progress.Node) !void { const command: CommandData = blk: { if (params.len < 2) break :blk CommandData{}; @@ -90,7 +90,7 @@ pub fn handle_command(params: []const []const u8) !void { switch (command.cmd) { .List => try handle_list(command.param), - .Install => try install_version(command.subcmd, command.param), + .Install => try install_version(command.subcmd, command.param, root_node), .Use => try use_version(command.subcmd, command.param), .Remove => try remove_version(command.subcmd, command.param), .Version => try get_version(), @@ -182,7 +182,7 @@ fn handle_list(param: ?[]const u8) !void { } } -fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { +fn install_version(subcmd: ?[]const u8, param: ?[]const u8, root_node: std.Progress.Node) !void { if (subcmd) |scmd| { var is_zls: bool = undefined; @@ -200,12 +200,10 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { return; }; - try install.install(version, is_zls); + try install.install(version, is_zls, root_node); } else if (param) |version| { // set zig version - try install.install(version, false); - // set zls version - //try install.install(version, true); + try install.install(version, false, root_node); } else { std.debug.print("Please specify a version to install: 'install zig/zls ' or 'install '.\n", .{}); } diff --git a/src/install.zig b/src/install.zig index 4468568..f1b2e95 100644 --- a/src/install.zig +++ b/src/install.zig @@ -20,16 +20,16 @@ const Version = struct { }; /// try install specified version -pub fn install(version: []const u8, is_zls: bool) !void { +pub fn install(version: []const u8, is_zls: bool, root_node: Progress.Node) !void { if (is_zls) { try install_zls(version); } else { - try install_zig(version); + try install_zig(version, root_node); } } /// Try to install the specified version of zig -fn install_zig(version: []const u8) !void { +fn install_zig(version: []const u8, root_node: Progress.Node) !void { var allocator = util_data.get_allocator(); const platform_str = try util_arch.platform_str(.{ @@ -43,15 +43,6 @@ fn install_zig(version: []const u8) !void { const arena_allocator = arena.allocator(); - // Determine the number of steps - const total_steps = 6; - - // Initialize progress root node - const root_node = std.Progress.start(.{ - .root_name = "Installing Zig", - .estimated_total_items = total_steps, - }); - var items_done: usize = 0; // Step 1: Get version path @@ -85,9 +76,9 @@ fn install_zig(version: []const u8) !void { const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; // Create a child progress node for the download - const download_node = root_node.start("Downloading Zig tarball", version_data.size); - const tarball_file = try util_http.download(parsed_uri, file_name, version_data.shasum, version_data.size); - defer tarball_file.close(); + const download_node = root_node.start("download zig", version_data.size); + const tarball_file = try util_http.download(parsed_uri, file_name, version_data.shasum, version_data.size, download_node); + // defer tarball_file.close(); download_node.end(); items_done += 1; @@ -110,10 +101,10 @@ fn install_zig(version: []const u8) !void { ); // Create a child progress node for the signature download - const sig_download_node = root_node.start("Downloading Signature File", 0); - const minisig_file = try util_http.download(signature_uri, signature_file_name, null, null); + const sig_download_node = root_node.start("verifying file signature", 0); + const minisig_file = try util_http.download(signature_uri, signature_file_name, null, null, sig_download_node); defer minisig_file.close(); - sig_download_node.end(); + // sig_download_node.end(); items_done += 1; root_node.setCompletedItems(items_done); @@ -133,10 +124,10 @@ fn install_zig(version: []const u8) !void { root_node.setCompletedItems(items_done); // Proceed with extraction after successful verification - const extract_node = root_node.start("Extracting Zig tarball", 0); + const extract_node = root_node.start("Extracting zig", 0); try util_tool.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - try util_extract.extract(extract_dir, tarball_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); + try util_extract.extract(extract_dir, tarball_file, if (builtin.os.tag == .windows) .zip else .tarxz, false, extract_node); extract_node.end(); items_done += 1; root_node.setCompletedItems(items_done); @@ -174,24 +165,44 @@ fn install_zls(version: []const u8) !void { const arena_allocator = arena.allocator(); - // get version path + // Determine total steps + const total_steps = 4; + + // Initialize progress root node + const root_node = std.Progress.start(.{ + .root_name = "Installing ZLS", + .estimated_total_items = total_steps, + }); + + var items_done: usize = 0; + + // Step 1: Get version path const version_path = try util_data.get_zvm_zls_version(arena_allocator); - // get extract path + items_done += 1; + root_node.setCompletedItems(items_done); + + // Step 2: Get extract path const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, true_version }); + items_done += 1; + root_node.setCompletedItems(items_done); if (util_tool.does_path_exist(extract_path)) { try alias.set_version(true_version, true); + root_node.end(); return; } - // get version data + // Step 3: Get version data const version_data: meta.Zls.VersionData = blk: { const res = try util_http.http_get(arena_allocator, config.zls_url); var zls_meta = try meta.Zls.init(res, arena_allocator); const tmp_val = try zls_meta.get_version_data(true_version, reverse_platform_str, arena_allocator); break :blk tmp_val orelse return error.UnsupportedVersion; }; + items_done += 1; + root_node.setCompletedItems(items_done); + // Step 4: Download the tarball const file_name = try std.mem.concat( arena_allocator, u8, @@ -199,16 +210,23 @@ fn install_zls(version: []const u8) !void { ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; - const new_file = try util_http.download(parsed_uri, file_name, null, version_data.size); + + // Create a child progress node for the download + const download_node = root_node.start("Downloading ZLS tarball", version_data.size); + const new_file = try util_http.download(parsed_uri, file_name, null, version_data.size, download_node); defer new_file.close(); + download_node.end(); + items_done += 1; + root_node.setCompletedItems(items_done); + // Proceed with extraction + const extract_node = root_node.start("Extracting ZLS tarball", 0); try util_tool.try_create_path(extract_path); - const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) - .zip - else - .tarxz, true); + try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, true, extract_node); + extract_node.end(); + items_done += 1; + root_node.setCompletedItems(items_done); try alias.set_version(true_version, true); } diff --git a/src/main.zig b/src/main.zig index b0b105c..292ff2d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,6 +7,12 @@ pub fn main() !void { // this will detect the memory whether leak defer if (gpa.deinit() == .leak) @panic("memory leaked!"); + // Initialize the root progress node + const root_node = std.Progress.start(.{ + .root_name = "zig installation", + .estimated_total_items = 6, + }); + // init some useful data try util_data.data_init(gpa.allocator()); // deinit some data @@ -23,5 +29,5 @@ pub fn main() !void { try command.handle_alias(args); // parse the args and handle command - try command.handle_command(args); + try command.handle_command(args, root_node); } diff --git a/src/util/extract.zig b/src/util/extract.zig index cbf743b..5112bf1 100644 --- a/src/util/extract.zig +++ b/src/util/extract.zig @@ -7,35 +7,46 @@ const tool = @import("tool.zig"); const xz = std.compress.xz; const tar = std.tar; -/// extract file to out_dir +/// Extract file to out_dir pub fn extract( out_dir: std.fs.Dir, file: std.fs.File, file_type: enum { tarxz, zip }, is_zls: bool, + root_node: std.Progress.Node, ) !void { switch (file_type) { - .zip => try extract_zip_dir(out_dir, file), - .tarxz => try extract_tarxz_to_dir(out_dir, file, is_zls), + .zip => try extract_zip_dir(out_dir, file, root_node), + .tarxz => try extract_tarxz_to_dir(out_dir, file, is_zls, root_node), } } -/// extract tar.xz to dir -fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File, is_zls: bool) !void { +/// Extract tar.xz to dir +fn extract_tarxz_to_dir( + out_dir: std.fs.Dir, + file: std.fs.File, + is_zls: bool, + root_node: std.Progress.Node, +) !void { var buffered_reader = std.io.bufferedReader(file.reader()); var decompressed = try xz.decompress(data.get_allocator(), buffered_reader.reader()); defer decompressed.deinit(); + // Start extraction with an indeterminate progress indicator + root_node.setEstimatedTotalItems(0); + try tar.pipeToFileSystem( out_dir, decompressed.reader(), .{ .mode_mode = .executable_bit_only, .strip_components = if (is_zls) 0 else 1 }, ); + + root_node.setCompletedItems(1); } -/// extract zip to directory -fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { +/// Extract zip to directory +fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File, _: std.Progress.Node) !void { var arena = std.heap.ArenaAllocator.init(data.get_allocator()); defer arena.deinit(); diff --git a/src/util/http.zig b/src/util/http.zig index da16f1f..3f35175 100644 --- a/src/util/http.zig +++ b/src/util/http.zig @@ -29,36 +29,31 @@ pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { return res; } -/// download the url -/// and verify hashsum (if exist) +/// Download the url and verify hashsum (if exist) pub fn download( uri: std.Uri, file_name: []const u8, shasum: ?[64]u8, size: ?usize, + progress_node: std.Progress.Node, ) !std.fs.File { - - // whether verify hashsum + // Whether to verify hashsum const if_hash = shasum != null; - - // allocator const allocator = data.get_allocator(); - // this file store the downloaded src + // Path to store the downloaded file const zvm_path = try data.get_zvm_path_segment(allocator, "store"); defer allocator.free(zvm_path); var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); defer store.close(); - // if file exist - // and provide shasum - // then calculate hash and verify, return the file if eql - // otherwise delete this file + // Check if the file already exists and verify its hash if (tool.does_path_exist2(store, file_name)) { if (if_hash) { var sha256 = std.crypto.hash.sha2.Sha256.init(.{}); const file = try store.openFile(file_name, .{}); + defer file.close(); var buffer: [512]u8 = undefined; while (true) { const byte_nums = try file.read(&buffer); @@ -72,17 +67,18 @@ pub fn download( if (hash.verify_hash(result, shasum.?)) { try file.seekTo(0); + progress_node.end(); return file; } } try store.deleteFile(file_name); } - // http client + // HTTP client var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); - var header_buffer: [10240]u8 = undefined; // 1024b + var header_buffer: [10240]u8 = undefined; var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); defer req.deinit(); @@ -90,41 +86,57 @@ pub fn download( try req.send(); try req.wait(); - // ensure req successfully + // Ensure request was successful if (req.response.status != .ok) return error.DownFailed; // Compare file sizes - if (size) |ss| { - const total_size: usize = @intCast(req.response.content_length orelse 0); - if (ss != total_size) + const total_size: usize = @intCast(req.response.content_length orelse 0); + if (size) |expected_size| { + if (expected_size != total_size) return error.IncorrectSize; } - // create a new file + // Set total items for progress reporting + if (total_size != 0) { + progress_node.setCompletedItems(total_size); + } + + // Create a new file const new_file = try store.createFile(file_name, .{ .read = true, }); + defer new_file.close(); - // whether enable hashsum + // Initialize hashsum if needed var sha256 = if (if_hash) std.crypto.hash.sha2.Sha256.init(.{}) else undefined; - // the tmp buffer to store the receive data - var buffer: [512]u8 = undefined; - // get reader + // Buffer for reading data + var buffer: [4096]u8 = undefined; const reader = req.reader(); + + var bytes_downloaded: usize = 0; + while (true) { - // the read byte number + // Read data from the response const byte_nums = try reader.read(&buffer); if (byte_nums == 0) break; + if (if_hash) sha256.update(buffer[0..byte_nums]); - // write to file + + // Write to file try new_file.writeAll(buffer[0..byte_nums]); + + // Update progress + bytes_downloaded += byte_nums; + if (total_size != 0) { + progress_node.setCompletedItems(bytes_downloaded); + } } - // when calculate hashsum + // Verify hashsum if needed if (if_hash) { var result = std.mem.zeroes([32]u8); sha256.final(&result); @@ -135,5 +147,8 @@ pub fn download( try new_file.seekTo(0); - return new_file; + progress_node.end(); + + // Re-open the file for reading + return try store.openFile(file_name, .{}); }