size: 2 KiB

const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const t = std.testing;

pub fn println(comptime fmt: []const u8, args: anytype) void {
    std.debug.print(fmt ++ "\n", args);
}

pub fn dirSize(gpa: mem.Allocator, dir: fs.Dir) !u64 {
    var walker = try dir.walk(gpa);
    defer walker.deinit();

    var size: u64 = 0;
    while (try walker.next()) |entry| {
        if (entry.kind != .sym_link) {
            size += (try dir.statFile(entry.path)).size;
        }
    }
    return size;
}

/// Not ideal because it truncates instead of rounding to the nearest value.
pub fn humanReadableSize(arena: mem.Allocator, n_bytes: u64) ![]const u8 {
    const units: []const []const u8 = &.{ "B", "KiB", "MiB", "GiB", "TiB" };
    const multiplier: u64 = 1024;
    var threshold: u64 = multiplier;
    var unitIdx: usize = 0;

    for (units, 0..) |_, i| {
        unitIdx = i;
        if (n_bytes < threshold) {
            break;
        }
        threshold *= multiplier;
    }

    return std.fmt.allocPrint(
        arena,
        "{d} {s}",
        .{
            n_bytes / std.math.pow(u64, 1024, unitIdx),
            units[unitIdx],
        },
    );
}

test "humanReadableSize" {
    var arena_impl = std.heap.ArenaAllocator.init(t.allocator_instance.allocator());
    defer arena_impl.deinit();
    const arena = arena_impl.allocator();

    try t.expectEqualStrings("12 B", try humanReadableSize(arena, 12));
    try t.expectEqualStrings("1023 B", try humanReadableSize(arena, 1023));
    try t.expectEqualStrings("1 KiB", try humanReadableSize(arena, 1024));
    try t.expectEqualStrings("1 KiB", try humanReadableSize(arena, 1024 + 1023));
    try t.expectEqualStrings("2 KiB", try humanReadableSize(arena, 1024 + 1024));
    try t.expectEqualStrings("2 MiB", try humanReadableSize(arena, 1024 * 1024 * 2));
    try t.expectEqualStrings("3 GiB", try humanReadableSize(arena, 1024 * 1024 * 1024 * 3));
    try t.expectEqualStrings("4 TiB", try humanReadableSize(arena, 1024 * 1024 * 1024 * 1024 * 4));
    try t.expectEqualStrings("1024 TiB", try humanReadableSize(arena, 1024 * 1024 * 1024 * 1024 * 1024));
}

pub const BlobType = enum { image, video, other };

pub fn blobType(path: []const u8) BlobType {
    const img_exts: []const []const u8 = &.{
        ".apng",
        ".avif",
        ".gif",
        ".jpeg",
        ".jpg",
        ".png",
        ".svg",
        ".webp",
        ".bmp",
    };
    for (img_exts) |ext| {
        if (std.ascii.endsWithIgnoreCase(path, ext)) return .image;
    }

    const video_exts: []const []const u8 = &.{
        ".mp4",
        ".webm",
    };
    for (video_exts) |ext| {
        if (std.ascii.endsWithIgnoreCase(path, ext)) return .video;
    }

    return .other;
}