1package main 2 3import ( 4 "encoding/gob" 5 "errors" 6 "os" 7 "path/filepath" 8 "sync" 9 "time" 10) 11 12var defaultLockFile = filepath.Join(userCacheDir, "o/lockfile.txt") 13 14// LockKeeper keeps track of which files are currently being edited by o 15type LockKeeper struct { 16 lockedFiles map[string]time.Time // from filename to lockfilestamp 17 mut *sync.RWMutex 18 lockFilename string 19} 20 21// NewLockKeeper takes an expanded path (not containing ~) to a lock file 22// and creates a new LockKeeper struct, without loading the given lock file. 23func NewLockKeeper(lockFilename string) *LockKeeper { 24 lockMap := make(map[string]time.Time) 25 return &LockKeeper{lockMap, &sync.RWMutex{}, lockFilename} 26} 27 28// Load loads the contents of the main lockfile 29func (lk *LockKeeper) Load() error { 30 f, err := os.Open(lk.lockFilename) 31 if err != nil { 32 return err 33 } 34 defer f.Close() 35 36 f.Sync() 37 38 dec := gob.NewDecoder(f) 39 lockMap := make(map[string]time.Time) 40 err = dec.Decode(&lockMap) 41 if err != nil { 42 return err 43 } 44 lk.mut.Lock() 45 lk.lockedFiles = lockMap 46 lk.mut.Unlock() 47 return nil 48} 49 50// Save writes the contents of the main lockfile 51func (lk *LockKeeper) Save() error { 52 // First create the folder for the lock file overview, if needed 53 folderPath := filepath.Dir(lk.lockFilename) 54 os.MkdirAll(folderPath, os.ModePerm) 55 56 f, err := os.OpenFile(lk.lockFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 57 if err != nil { 58 return err 59 } 60 defer f.Close() 61 62 enc := gob.NewEncoder(f) 63 64 lk.mut.RLock() 65 err = enc.Encode(lk.lockedFiles) 66 lk.mut.RUnlock() 67 68 f.Sync() 69 70 return err 71} 72 73// Lock marks the given absolute filename as locked. 74// If the file is already locked, an error is returned. 75func (lk *LockKeeper) Lock(filename string) error { 76 var has bool 77 lk.mut.RLock() 78 _, has = lk.lockedFiles[filename] 79 lk.mut.RUnlock() 80 81 if has { 82 return errors.New("already locked: " + filename) 83 } 84 85 // Add the file to the map 86 lk.mut.Lock() 87 lk.lockedFiles[filename] = time.Now() 88 lk.mut.Unlock() 89 90 return nil 91} 92 93// Unlock marks the given absolute filename as unlocked. 94// If the file is already unlocked, an error is returned. 95func (lk *LockKeeper) Unlock(filename string) error { 96 var has bool 97 lk.mut.RLock() 98 _, has = lk.lockedFiles[filename] 99 lk.mut.RUnlock() 100 101 if !has { 102 // Caller can ignore this error if they want 103 return errors.New("already unlocked: " + filename) 104 } 105 106 // Remove the file from the map 107 lk.mut.Lock() 108 delete(lk.lockedFiles, filename) 109 lk.mut.Unlock() 110 111 return nil 112} 113 114// GetTimestamp assumes that the file is locked. A blank timestamp may be returned if not. 115func (lk *LockKeeper) GetTimestamp(filename string) time.Time { 116 var timestamp time.Time 117 118 lk.mut.RLock() 119 timestamp = lk.lockedFiles[filename] 120 lk.mut.RUnlock() 121 122 return timestamp 123} 124