const std = @import("std"); const Zip = @import("zip"); const SeekableHttpRange = @import("seekable_http_range.zig"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const a = gpa.allocator(); var args = try std.process.argsAlloc(a); defer std.process.argsFree(a, args); if (args.len < 2) { try printHelpAndExit("No URL specified"); return; } if (args.len < 3) { try printHelpAndExit("No FILES specified"); return; } const url = args[1]; const files = args[2..]; // TODO general purpose options handling if (std.mem.eql(u8, url, "-h") or std.mem.eql(u8, url, "--help")) { try printHelpAndExit(null); return; } var client = std.http.Client{.allocator = a}; defer client.deinit(); var range = try SeekableHttpRange.init(.{ .allocator = a, .client = &client, .url = url, .buffer_size = 8192, }); defer range.deinit(); var zip = try Zip.from(a, &range); defer zip.deinit(); var at_least_one_success = false; for (files) |file| { cdhlp: for (zip.central_directory_headers.items, 0..) |cdh, ix| { if (std.mem.eql(u8, file, cdh.file_name)) { const cwd = std.fs.cwd(); if (std.fs.path.dirname(file)) |dirname| { try cwd.makePath(dirname); } var outfile = try cwd.createFile(cdh.file_name, .{}); defer outfile.close(); try zip.extract(ix, &range, outfile.writer()); at_least_one_success = true; break :cdhlp; } } else { try std.fmt.format(std.io.getStdErr().writer(), "File {s} not found in zip\n", .{file}); } } if (!at_least_one_success) { try std.io.getStdErr().writeAll("No files successfully downloded\n"); std.os.exit(1); } } fn printHelpAndExit(comptime errmsg: ?[]const u8) !void { const usage = \\Usage: \\zipdl URL FILES... \\ URL is the http(s) URL of a ZIP file \\ FILES are the files to be extracted from the remote ZIP \\ ; if (errmsg) |err| { try std.io.getStdErr().writeAll(err); try std.io.getStdErr().writeAll("\n\n"); try std.io.getStdErr().writeAll(usage); std.os.exit(2); } else { try std.io.getStdErr().writeAll(usage); std.os.exit(0); } unreachable; } test { _ = SeekableHttpRange; }