1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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();
}
|