about summary refs log tree commit diff
path: root/src/FileSwapper.zig
blob: 13bafe49c7357f2b1df17aa856a5f13cfedc8a83 (plain) (blame)
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();
}