| 1 | const Atomic = @This(); |
| 2 | |
| 3 | const std = @import("../../std.zig"); |
| 4 | const Io = std.Io; |
| 5 | const File = std.Io.File; |
| 6 | const Dir = std.Io.Dir; |
| 7 | const assert = std.debug.assert; |
| 8 | |
| 9 | file: File, |
| 10 | file_basename_hex: u64, |
| 11 | file_open: bool, |
| 12 | file_exists: bool, |
| 13 | |
| 14 | dir: Dir, |
| 15 | close_dir_on_deinit: bool, |
| 16 | |
| 17 | dest_sub_path: []const u8, |
| 18 | |
| 19 | pub const InitError = File.OpenError; |
| 20 | |
| 21 | /// To release all resources, always call `deinit`, even after a successful |
| 22 | /// `finish`. |
| 23 | pub fn deinit(af: *Atomic, io: Io) void { |
| 24 | if (af.file_open) { |
| 25 | af.file.close(io); |
| 26 | af.file_open = false; |
| 27 | } |
| 28 | if (af.file_exists) { |
| 29 | const tmp_sub_path = std.fmt.hex(af.file_basename_hex); |
| 30 | af.dir.deleteFile(io, &tmp_sub_path) catch {}; |
| 31 | af.file_exists = false; |
| 32 | } |
| 33 | if (af.close_dir_on_deinit) { |
| 34 | af.dir.close(io); |
| 35 | af.close_dir_on_deinit = false; |
| 36 | } |
| 37 | af.* = undefined; |
| 38 | } |
| 39 | |
| 40 | pub const LinkError = File.HardLinkError || Dir.RenamePreserveError; |
| 41 | |
| 42 | /// Atomically materializes the file into place, failing with |
| 43 | /// `error.PathAlreadyExists` if something already exists there. |
| 44 | /// |
| 45 | /// If this operation could not be done with an unnamed temporary file, the |
| 46 | /// named temporary file will be deleted in a following operation, which may |
| 47 | /// independently fail. The result of that operation is stored in `delete_err`. |
| 48 | pub fn link(af: *Atomic, io: Io) LinkError!void { |
| 49 | if (af.file_exists) { |
| 50 | if (af.file_open) { |
| 51 | af.file.close(io); |
| 52 | af.file_open = false; |
| 53 | } |
| 54 | const tmp_sub_path = std.fmt.hex(af.file_basename_hex); |
| 55 | try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io); |
| 56 | af.file_exists = false; |
| 57 | } else { |
| 58 | assert(af.file_open); |
| 59 | try af.file.hardLink(io, af.dir, af.dest_sub_path, .{}); |
| 60 | af.file.close(io); |
| 61 | af.file_open = false; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | pub const ReplaceError = Dir.RenameError; |
| 66 | |
| 67 | /// Atomically materializes the file into place, replacing any file that |
| 68 | /// already exists there. |
| 69 | /// |
| 70 | /// Calling this function requires setting `CreateFileAtomicOptions.replace` to |
| 71 | /// `true`. |
| 72 | /// |
| 73 | /// On Windows, this function introduces a period of time where some file |
| 74 | /// system operations on the destination file will result in |
| 75 | /// `error.AccessDenied`, including rename operations (such as the one used in |
| 76 | /// this function). |
| 77 | pub fn replace(af: *Atomic, io: Io) ReplaceError!void { |
| 78 | assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`. |
| 79 | if (af.file_open) { |
| 80 | af.file.close(io); |
| 81 | af.file_open = false; |
| 82 | } |
| 83 | const tmp_sub_path = std.fmt.hex(af.file_basename_hex); |
| 84 | try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io); |
| 85 | af.file_exists = false; |
| 86 | } |