master
go 64 lines 1.33 KB
Raw
1 package atomicfile
2
3 import (
4 "fmt"
5 "io"
6 "os"
7 "path/filepath"
8 )
9
10 // File represents an atomic file writer
11 type File struct {
12 *os.File
13 path string
14 }
15
16 // New creates a new atomic file writer
17 func New(path string, mode os.FileMode) (*File, error) {
18 dir := filepath.Dir(path)
19 tempFile, err := os.CreateTemp(dir, ".tmp-"+filepath.Base(path))
20 if err != nil {
21 return nil, err
22 }
23
24 if err := tempFile.Chmod(mode); err != nil {
25 tempFile.Close()
26 os.Remove(tempFile.Name())
27 return nil, err
28 }
29
30 return &File{
31 File: tempFile,
32 path: path,
33 }, nil
34 }
35
36 // Close atomically replaces the target file with the temporary file
37 func (f *File) Close() error {
38 closeErr := f.File.Close()
39 if closeErr != nil {
40 // Try to cleanup temp file, but prioritize close error
41 _ = os.Remove(f.File.Name())
42 return closeErr
43 }
44 return os.Rename(f.File.Name(), f.path)
45 }
46
47 // Abort removes the temporary file without replacing the target
48 func (f *File) Abort() error {
49 closeErr := f.File.Close()
50 removeErr := os.Remove(f.File.Name())
51
52 if closeErr != nil && removeErr != nil {
53 return fmt.Errorf("abort failed: close: %w, remove: %v", closeErr, removeErr)
54 }
55 if closeErr != nil {
56 return closeErr
57 }
58 return removeErr
59 }
60
61 // ReadFrom reads from the given reader into the atomic file
62 func (f *File) ReadFrom(r io.Reader) (int64, error) {
63 return io.Copy(f.File, r)
64 }