github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/journal.go (about) 1 // Copyright (c) 2017-2019 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package gitbe 6 7 import ( 8 "bufio" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 ) 16 17 var ( 18 ErrBusy = fmt.Errorf("busy") 19 ErrNotFound = fmt.Errorf("not found") 20 ErrSameFile = fmt.Errorf("source same as destination") 21 ) 22 23 type journalFile struct { 24 file *os.File 25 scanner *bufio.Scanner 26 } 27 28 // Journal is a generic 1:N file journaler. It uses an in memory mutex to 29 // coordinate all actions on disk. The journaler assumes text files that are 30 // "\n" delineated. 31 type Journal struct { 32 sync.Mutex 33 journals map[string]*journalFile 34 } 35 36 // NewJournal creates a new Journal context. One can use a single journal 37 // context for many journals. 38 func NewJournal() *Journal { 39 return &Journal{ 40 journals: make(map[string]*journalFile), 41 } 42 } 43 44 // Journal writes content to a journal file. Note that content should not be 45 // bigger than bufio.Scanner can read per line. If the user does not provide 46 // "\n" at the end of content string, this function appends it. 47 func (j *Journal) Journal(filename, content string) error { 48 j.Lock() 49 defer j.Unlock() 50 51 if _, ok := j.journals[filename]; ok { 52 return ErrBusy 53 } 54 55 if !strings.HasSuffix(content, "\n") { 56 content += "\n" 57 } 58 59 f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0640) 60 if err != nil { 61 return err 62 } 63 defer f.Sync() 64 defer f.Close() 65 66 _, err = f.Write([]byte(content)) 67 return err 68 } 69 70 // Open opens a journal file ready for replay. Once done replaying the journal 71 // the journal file needs to be closed. Note that if the journal is open writes 72 // return ErrBusy. 73 func (j *Journal) Open(filename string) error { 74 j.Lock() 75 defer j.Unlock() 76 77 if _, ok := j.journals[filename]; ok { 78 return ErrBusy 79 } 80 81 f, err := os.Open(filename) 82 if err != nil { 83 return err 84 } 85 86 j.journals[filename] = &journalFile{ 87 file: f, 88 scanner: bufio.NewScanner(f), 89 } 90 91 return nil 92 } 93 94 // Close closes the underlying journal file. If the journal file is not found 95 // the function returns ErrNotFound. 96 func (j *Journal) Close(filename string) error { 97 j.Lock() 98 defer j.Unlock() 99 100 f, ok := j.journals[filename] 101 if !ok { 102 return ErrNotFound 103 } 104 delete(j.journals, filename) 105 return f.file.Close() 106 } 107 108 // Replay reads a single line from the journal file and calls the replay 109 // function that was provided. If the scanner encounters EOF it returns EOF, 110 // unlike the scanner API. 111 func (j *Journal) Replay(filename string, replay func(string) error) error { 112 j.Lock() 113 f, ok := j.journals[filename] 114 if !ok { 115 j.Unlock() 116 return ErrNotFound 117 } 118 j.Unlock() 119 120 // We can run unlocked from here 121 122 if !f.scanner.Scan() { 123 if f.scanner.Err() == nil { 124 return io.EOF 125 } 126 return f.scanner.Err() 127 } 128 129 return replay(f.scanner.Text()) 130 } 131 132 // Copy copies a journal file from source to destination. During the copy 133 // process the source file remains locked. This call has the same error 134 // semantics as the Open call. 135 func (j *Journal) Copy(source, destination string) (err error) { 136 err = j.Open(source) 137 if err != nil { 138 return 139 } 140 defer func() { 141 cerr := j.Close(source) 142 if cerr != nil { 143 err = cerr 144 } 145 }() 146 147 err = fileCopy(source, destination) 148 return 149 } 150 151 // fileCopy copies a file from src to dst. It attempts to detect if the source 152 // and destination filename are the same and will return ErrSameFile if they 153 // are. This test can be defeated and it is meant as basic bug detector. 154 func fileCopy(srcName, dstName string) (err error) { 155 // Try a little bit to prevent overwriting the same file. 156 src := filepath.Clean(srcName) 157 dst := filepath.Clean(dstName) 158 if src == dst { 159 err = ErrSameFile 160 return 161 } 162 var in, out *os.File 163 in, err = os.Open(src) 164 if err != nil { 165 return 166 } 167 defer in.Close() 168 out, err = os.Create(dst) 169 if err != nil { 170 return 171 } 172 defer func() { 173 cerr := out.Close() 174 if cerr == nil { 175 err = cerr 176 } 177 }() 178 if _, err = io.Copy(out, in); err != nil { 179 return 180 } 181 err = out.Sync() 182 return 183 }