github.com/scottcagno/storage@v1.8.0/pkg/_junk/_lsmtree/encoding/binary/datafile.go (about) 1 package binary 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "sync" 9 ) 10 11 var ( 12 ErrFileClosed = errors.New("error: file is closed") 13 ErrBadEntry = errors.New("error: bad entry") 14 ) 15 16 // DataFile is is a syncronized binary reader and writer 17 type DataFile struct { 18 sync.RWMutex 19 r *Reader 20 w *Writer 21 } 22 23 // OpenDataFile opens and returns a new datafile 24 func OpenDataFile(path string) (*DataFile, error) { 25 // create and sanitize the path 26 path, err := Touch(path) 27 if err != nil { 28 return nil, err 29 } 30 // open a new reader 31 r, err := OpenReader(path) 32 if err != nil { 33 return nil, err 34 } 35 // open a new writer 36 w, err := OpenWriter(path) 37 if err != nil { 38 return nil, err 39 } 40 // init data file and return 41 return &DataFile{ 42 r: r, 43 w: w, 44 }, nil 45 } 46 47 // WriteEntry writes an entry in an append-only fashion 48 func (d *DataFile) WriteEntry(de *DataEntry) (int64, error) { 49 // lock 50 d.Lock() 51 defer d.Unlock() 52 // write entry (sequentially, append-only) 53 return d.w.WriteEntry(de) 54 } 55 56 // WriteEntryIndex writes an entry in an append-only fashion 57 func (d *DataFile) WriteEntryIndex(ei *EntryIndex) (int64, error) { 58 // lock 59 d.Lock() 60 defer d.Unlock() 61 // write entry (sequentially, append-only) 62 return d.w.WriteEntryIndex(ei) 63 } 64 65 // ReadEntry attempts to read and return the next entry sequentially 66 func (d *DataFile) ReadEntry() (*DataEntry, error) { 67 // read lock 68 d.RLock() 69 defer d.RUnlock() 70 // read next entry sequentially 71 return d.r.ReadEntry() 72 } 73 74 // ReadEntryIndex attempts to read and return the next entry sequentially 75 func (d *DataFile) ReadEntryIndex() (*EntryIndex, error) { 76 // read lock 77 d.RLock() 78 defer d.RUnlock() 79 // read next entry sequentially 80 return d.r.ReadEntryIndex() 81 } 82 83 // ReadEntryAt attempts to read and return an entry at the specified offset 84 func (d *DataFile) ReadEntryAt(offset int64) (*DataEntry, error) { 85 // lock 86 d.RLock() 87 defer d.RUnlock() 88 // read entry at specified offset 89 return d.r.ReadEntryAt(offset) 90 } 91 92 // ReadEntryIndexAt attempts to read and return an entry at the specified offset 93 func (d *DataFile) ReadEntryIndexAt(offset int64) (*EntryIndex, error) { 94 // lock 95 d.RLock() 96 defer d.RUnlock() 97 // read entry at specified offset 98 return d.r.ReadEntryIndexAt(offset) 99 } 100 101 // Range iterates the entries as long as the provided boolean function is true 102 func (d *DataFile) Range(iter func(de *DataEntry) bool) error { 103 // lock 104 d.Lock() 105 defer d.Unlock() 106 // grab the reader's offset, so we can set it back later 107 offset, err := d.r.Offset() 108 if err != nil { 109 return err 110 } 111 // go to the beginning of the file 112 _, err = d.r.fd.Seek(0, io.SeekStart) 113 if err != nil { 114 return err 115 } 116 // start loop 117 for { 118 // read entry, and check for EOF 119 de, err := d.r.ReadEntry() 120 if err != nil { 121 if err == io.EOF || err == io.ErrUnexpectedEOF { 122 break 123 } 124 return err 125 } 126 // entry is good, lets pass it to our boolean function 127 if !iter(de) { 128 continue // if iter(e) returns false, continue to next entry 129 } 130 } 131 // we are done reading all the entries (hopefully), so 132 // we seek back to where we were at the start of this function 133 _, err = d.r.fd.Seek(offset, io.SeekStart) 134 if err != nil { 135 return err 136 } 137 return nil 138 } 139 140 // Close closes the DataFile 141 func (d *DataFile) Close() error { 142 // lock 143 d.Lock() 144 defer d.Unlock() 145 // close reader 146 err := d.r.Close() 147 if err != nil { 148 return err 149 } 150 // close writer 151 err = d.w.Close() 152 if err != nil { 153 return err 154 } 155 return nil 156 } 157 158 // Touch cleans and initializes the path, files and folders 159 func Touch(path string) (string, error) { 160 // get absolute path 161 path, err := filepath.Abs(path) 162 if err != nil { 163 return "", err 164 } 165 // convert any slashes 166 path = filepath.ToSlash(path) 167 // check to see if the path exists 168 _, err = os.Stat(path) 169 if os.IsNotExist(err) { 170 // create dirs if they need creating 171 dir, file := filepath.Split(path) 172 err = os.MkdirAll(dir, os.ModeDir) 173 if err != nil { 174 return "", err 175 } 176 // create and files if the need creating 177 fd, err := os.Create(dir + file) 178 if err != nil { 179 return "", err 180 } 181 // close, because we are just touching them 182 err = fd.Close() 183 if err != nil { 184 return "", err 185 } 186 } 187 // return sanitized path (creating any files or folders) 188 return path, nil 189 } 190 191 // Offset is a helper function that returns the current 192 // offset of the provided reader or writer 193 func Offset(rw io.ReadWriteSeeker) (int64, error) { 194 return rw.Seek(0, io.SeekCurrent) 195 }