diff options
| author | Robert Günzler <r@gnzler.io> | 2026-06-24 11:57:02 +0900 |
|---|---|---|
| committer | Robert Günzler <r@gnzler.io> | 2026-09-22 18:01:30 +0200 |
| commit | c677e8873e0eae790a1b8b07844a10efa9f63a1e (patch) | |
| tree | 95bc9709c374d27962a73712d24807a98cc69bb4 /src/FileSwapper.zig | |
| parent | fcdf352af595759aa34e6ed037ee10810867a616 (diff) | |
| download | site-c677e8873e0eae790a1b8b07844a10efa9f63a1e.tar.gz | |
wip design for v2
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to '')
| -rw-r--r-- | src/FileSwapper.zig | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/src/FileSwapper.zig b/src/FileSwapper.zig new file mode 100644 index 0000000..13bafe4 --- /dev/null +++ b/src/FileSwapper.zig @@ -0,0 +1,101 @@ +const std = @import("std"); + +const FileSwapper = @This(); +const log = std.log.scoped(.FileSwapper); + +io: std.Io, +arena: *std.heap.ArenaAllocator, +dir: std.Io.Dir, +target_path: []const u8, +tmp_path: []const u8, +tmp: std.Io.File, + +pub fn createAt(io: std.Io, allocator: std.mem.Allocator, dir: std.Io.Dir, sub_path: []const u8) !FileSwapper { + const arena = try allocator.create(std.heap.ArenaAllocator); + errdefer allocator.destroy(arena); + arena.* = std.heap.ArenaAllocator.init(allocator); + + const tmp_path = try std.fmt.allocPrint( + arena.allocator(), + ".{s}", + .{sub_path}, + ); + + log.debug("Creating {s}", .{tmp_path}); + + const f = try dir.createFile(io, tmp_path, .{}); + + return .{ + .io = io, + .arena = arena, + .dir = dir, + .target_path = try arena.allocator().dupe(u8, sub_path), + .tmp_path = tmp_path, + .tmp = f, + }; +} + +fn deinit(self: FileSwapper) void { + self.tmp.close(self.io); + const allocator = self.arena.child_allocator; + self.arena.deinit(); + allocator.destroy(self.arena); +} + +pub fn file(self: FileSwapper) std.fs.File { + return self.tmp; +} + +pub fn writer(self: FileSwapper) std.Io.File.Writer { + const buf = self.arena.allocator().alloc(u8, 1024) catch + @panic("oom"); + return self.tmp.writer(self.io, buf); +} + +pub fn swap(self: FileSwapper) !void { + log.debug("Swapping {s} -> {s}", .{ self.tmp_path, self.target_path }); + + // flush writes + try self.tmp.sync(self.io); + + // swap file into final location + // NOTE: the tmp fd is still valid after this + try std.Io.Dir.rename( + self.dir, + self.tmp_path, + self.dir, + self.target_path, + self.io, + ); +} + +pub fn close(self: FileSwapper) void { + self.swap() catch + @panic("failed to swap target file"); + + self.deinit(); +} + +test "smoke" { + std.testing.log_level = .debug; + + const alloc = std.testing.allocator; + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const fileswapper = try FileSwapper.createAt(std.testing.io, alloc, tmp.dir, "test.txt"); + errdefer fileswapper.deinit(); + + var w = fileswapper.writer(); + try w.interface.writeAll("hi"); + + try tmp.dir.access(std.testing.io, fileswapper.tmp_path, .{ .read = true, .write = true }); + + try fileswapper.swap(); + + const err = tmp.dir.access(std.testing.io, fileswapper.tmp_path, .{ .read = true, .write = true }); + try std.testing.expectError(error.FileNotFound, err); + + fileswapper.deinit(); +} |