github.com/mithrandie/csvq@v1.18.1/lib/file/control_file.go (about) 1 package file 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "time" 8 9 "github.com/mithrandie/go-file/v2" 10 ) 11 12 type ControlFileType int 13 14 const ( 15 RLock ControlFileType = iota 16 Lock 17 Temporary 18 ) 19 20 var controlFileTypeLit = map[ControlFileType]string{ 21 RLock: "read lock", 22 Lock: "lock", 23 Temporary: "temporary", 24 } 25 26 func (t ControlFileType) String() string { 27 return controlFileTypeLit[t] 28 } 29 30 type ControlFile struct { 31 path string 32 fp *os.File 33 } 34 35 func NewControlFile(path string, fp *os.File) *ControlFile { 36 return &ControlFile{ 37 path: path, 38 fp: fp, 39 } 40 } 41 42 func (m *ControlFile) Close() error { 43 if m != nil { 44 if m.fp != nil { 45 if err := file.Close(m.fp); err != nil { 46 return err 47 } 48 } 49 50 if Exists(m.path) { 51 if err := os.Remove(m.path); err != nil { 52 return err 53 } 54 } 55 } 56 return nil 57 } 58 59 func (m *ControlFile) CloseWithErrors() []error { 60 var errs []error 61 if m != nil { 62 if m.fp != nil { 63 if err := file.Close(m.fp); err != nil { 64 errs = append(errs, err) 65 } 66 } 67 68 if Exists(m.path) { 69 if err := os.Remove(m.path); err != nil { 70 errs = append(errs, err) 71 } 72 } 73 } 74 return errs 75 } 76 77 func CreateControlFileContext(ctx context.Context, filePath string, fileType ControlFileType, retryDelay time.Duration) (*ControlFile, error) { 78 if ctx.Err() != nil { 79 if ctx.Err() == context.Canceled { 80 return nil, NewContextCanceled() 81 } 82 return nil, NewContextDone(ctx.Err().Error()) 83 } 84 85 for { 86 f, err := tryCreateControlFile(filePath, fileType) 87 if err == nil { 88 return f, nil 89 } 90 if _, ok := err.(*LockError); !ok { 91 return nil, err 92 } 93 94 select { 95 case <-ctx.Done(): 96 if ctx.Err() == context.Canceled { 97 return nil, NewContextCanceled() 98 } 99 return nil, NewTimeoutError(filePath) 100 case <-time.After(retryDelay): 101 // try again 102 } 103 } 104 } 105 106 func tryCreateControlFile(filePath string, fileType ControlFileType) (*ControlFile, error) { 107 if len(filePath) < 1 { 108 return nil, NewLockError("filename not specified") 109 } 110 111 switch fileType { 112 case Lock: 113 return TryCreateLockFile(filePath) 114 case Temporary: 115 return TryCreateTempFile(filePath) 116 default: //RLock 117 return TryCreateRLockFile(filePath) 118 } 119 } 120 121 func TryCreateRLockFile(filePath string) (controlFile *ControlFile, err error) { 122 if LockExists(filePath) { 123 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath)) 124 } 125 126 lockFilePath := LockFilePath(filePath) 127 lfp, err := file.Create(lockFilePath) 128 if err != nil { 129 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath)) 130 } 131 lockFile := NewControlFile(lockFilePath, lfp) 132 defer func() { 133 err = NewCompositeError(err, lockFile.Close()) 134 }() 135 136 rlockFilePath := RLockFilePath(filePath) 137 fp, e := file.Create(rlockFilePath) 138 if e != nil { 139 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", RLock, filePath)) 140 } 141 142 return NewControlFile(rlockFilePath, fp), nil 143 } 144 145 func TryCreateLockFile(filePath string) (*ControlFile, error) { 146 if LockExists(filePath) || RLockExists(filePath) { 147 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath)) 148 } 149 150 lockFilePath := LockFilePath(filePath) 151 fp, err := file.Create(lockFilePath) 152 if err != nil { 153 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath)) 154 } 155 lockFile := NewControlFile(lockFilePath, fp) 156 157 if RLockExists(filePath) { 158 err := NewLockError(fmt.Sprintf("failed to create %s file for %q", Lock, filePath)) 159 err = NewCompositeError(err, lockFile.Close()) 160 return nil, err 161 } 162 163 return lockFile, nil 164 } 165 166 func TryCreateTempFile(filePath string) (*ControlFile, error) { 167 tempFilePath := TempFilePath(filePath) 168 fp, err := file.Create(tempFilePath) 169 if err != nil { 170 return nil, NewLockError(fmt.Sprintf("failed to create %s file for %q", Temporary, filePath)) 171 } 172 173 return NewControlFile(tempFilePath, fp), nil 174 }