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(); }