github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/store/file/file.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/store/file/file.go 14 15 // Package local is a file system backed store 16 package file 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "os" 22 "path/filepath" 23 "time" 24 25 "github.com/tickoalcantara12/micro/v3/service/store" 26 bolt "go.etcd.io/bbolt" 27 ) 28 29 var ( 30 // DefaultDatabase is the namespace that the bbolt store 31 // will use if no namespace is provided. 32 DefaultDatabase = "micro" 33 // DefaultTable when none is specified 34 DefaultTable = "micro" 35 // DefaultDir is the default directory for bbolt files 36 DefaultDir = filepath.Join(os.TempDir(), "micro", "store") 37 38 // bucket used for data storage 39 dataBucket = "data" 40 ) 41 42 // NewStore returns a file store 43 func NewStore(opts ...store.Option) store.Store { 44 s := &fileStore{} 45 s.init(opts...) 46 return s 47 } 48 49 type fileStore struct { 50 options store.Options 51 dir string 52 } 53 54 type fileHandle struct { 55 key string 56 db *bolt.DB 57 } 58 59 // record stored by us 60 type record struct { 61 Key string 62 Value []byte 63 Metadata map[string]interface{} 64 ExpiresAt time.Time 65 } 66 67 func key(database, table string) string { 68 return database + ":" + table 69 } 70 71 func (m *fileStore) delete(db *bolt.DB, key string) error { 72 return db.Update(func(tx *bolt.Tx) error { 73 b := tx.Bucket([]byte(dataBucket)) 74 if b == nil { 75 return nil 76 } 77 return b.Delete([]byte(key)) 78 }) 79 } 80 81 func (m *fileStore) init(opts ...store.Option) error { 82 for _, o := range opts { 83 o(&m.options) 84 } 85 86 if m.options.Database == "" { 87 m.options.Database = DefaultDatabase 88 } 89 90 if m.options.Table == "" { 91 // bbolt requires bucketname to not be empty 92 m.options.Table = DefaultTable 93 } 94 95 // Ignoring this as the folder might exist. 96 // Reads/Writes updates will return with sensible error messages 97 // about the dir not existing in case this cannot create the path anyway 98 dir := m.getDir(m.options.Database) 99 os.MkdirAll(dir, 0700) 100 return nil 101 } 102 103 // getDir returns the directory which should contain the files for a databases 104 func (m *fileStore) getDir(db string) string { 105 // get the directory option from the context 106 var directory string 107 if m.options.Context != nil { 108 fd, ok := m.options.Context.Value(dirKey{}).(string) 109 if ok { 110 directory = fd 111 } 112 } 113 if len(directory) == 0 { 114 directory = DefaultDir 115 } 116 117 // construct the directory, e.g. /tmp/micro 118 return filepath.Join(directory, db) 119 } 120 121 func (f *fileStore) getDB(database, table string) (*bolt.DB, error) { 122 if len(database) == 0 { 123 database = f.options.Database 124 } 125 if len(table) == 0 { 126 table = f.options.Table 127 } 128 129 // create a directory /tmp/micro 130 dir := f.getDir(database) 131 // create the database handle 132 fname := table + ".db" 133 // make the dir 134 os.MkdirAll(dir, 0700) 135 // database path 136 dbPath := filepath.Join(dir, fname) 137 138 // create new db handle 139 // Bolt DB only allows one process to open the file R/W so make sure we're doing this under a lock 140 return bolt.Open(dbPath, 0700, &bolt.Options{Timeout: 5 * time.Second}) 141 } 142 143 func (m *fileStore) list(db *bolt.DB, order store.Order, limit, offset uint, prefix, suffix string) []string { 144 var keys []string 145 146 db.View(func(tx *bolt.Tx) error { 147 b := tx.Bucket([]byte(dataBucket)) 148 // nothing to read 149 if b == nil { 150 return nil 151 } 152 c := b.Cursor() 153 var k, v []byte 154 var cont func(k []byte) bool 155 156 if prefix != "" { 157 // for prefix we can speed up the search, not for suffix though :( 158 k, v = c.Seek([]byte(prefix)) 159 cont = func(k []byte) bool { 160 return bytes.HasPrefix(k, []byte(prefix)) 161 } 162 } else { 163 k, v = c.First() 164 cont = func(k []byte) bool { 165 return true 166 } 167 } 168 169 // get all the key/vals 170 for ; k != nil && cont(k); k, v = c.Next() { 171 storedRecord := &record{} 172 173 if err := json.Unmarshal(v, storedRecord); err != nil { 174 return err 175 } 176 if !storedRecord.ExpiresAt.IsZero() { 177 if storedRecord.ExpiresAt.Before(time.Now()) { 178 continue 179 } 180 } 181 if suffix != "" && !bytes.HasSuffix(k, []byte(suffix)) { 182 continue 183 } 184 185 keys = append(keys, string(k)) 186 } 187 188 return nil 189 }) 190 191 // now check if we need do ordering 192 if order == store.OrderDesc { 193 for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 { 194 keys[i], keys[j] = keys[j], keys[i] 195 } 196 } 197 198 // build a new key set 199 var keyList []string 200 201 if offset > 0 { 202 // offset is greater than the keys we have 203 if int(offset) >= len(keys) { 204 return nil 205 } 206 207 // otherwise set the offset for the keys 208 keys = keys[offset:] 209 } 210 211 // check key limit 212 if v := int(limit); v == 0 || v > len(keys) { 213 limit = uint(len(keys)) 214 } 215 216 for i := 0; i < int(limit); i++ { 217 keyList = append(keyList, keys[i]) 218 } 219 220 return keyList 221 } 222 223 func (m *fileStore) get(db *bolt.DB, k string) (*store.Record, error) { 224 var value []byte 225 226 db.View(func(tx *bolt.Tx) error { 227 // @todo this is still very experimental... 228 b := tx.Bucket([]byte(dataBucket)) 229 if b == nil { 230 return nil 231 } 232 233 value = b.Get([]byte(k)) 234 return nil 235 }) 236 237 if value == nil { 238 return nil, store.ErrNotFound 239 } 240 241 storedRecord := &record{} 242 243 if err := json.Unmarshal(value, storedRecord); err != nil { 244 return nil, err 245 } 246 247 newRecord := &store.Record{} 248 newRecord.Key = storedRecord.Key 249 newRecord.Value = storedRecord.Value 250 newRecord.Metadata = make(map[string]interface{}) 251 252 for k, v := range storedRecord.Metadata { 253 newRecord.Metadata[k] = v 254 } 255 256 if !storedRecord.ExpiresAt.IsZero() { 257 if storedRecord.ExpiresAt.Before(time.Now()) { 258 return nil, store.ErrNotFound 259 } 260 newRecord.Expiry = time.Until(storedRecord.ExpiresAt) 261 } 262 263 return newRecord, nil 264 } 265 266 func (m *fileStore) set(db *bolt.DB, r *store.Record) error { 267 // copy the incoming record and then 268 // convert the expiry in to a hard timestamp 269 item := &record{} 270 item.Key = r.Key 271 item.Value = r.Value 272 item.Metadata = make(map[string]interface{}) 273 274 if r.Expiry != 0 { 275 item.ExpiresAt = time.Now().Add(r.Expiry) 276 } 277 278 for k, v := range r.Metadata { 279 item.Metadata[k] = v 280 } 281 282 // marshal the data 283 data, _ := json.Marshal(item) 284 285 return db.Update(func(tx *bolt.Tx) error { 286 b := tx.Bucket([]byte(dataBucket)) 287 if b == nil { 288 var err error 289 b, err = tx.CreateBucketIfNotExists([]byte(dataBucket)) 290 if err != nil { 291 return err 292 } 293 } 294 return b.Put([]byte(r.Key), data) 295 }) 296 } 297 298 func (f *fileStore) Close() error { 299 return nil 300 } 301 302 func (f *fileStore) Init(opts ...store.Option) error { 303 return f.init(opts...) 304 } 305 306 func (m *fileStore) Delete(key string, opts ...store.DeleteOption) error { 307 var deleteOptions store.DeleteOptions 308 for _, o := range opts { 309 o(&deleteOptions) 310 } 311 312 db, err := m.getDB(deleteOptions.Database, deleteOptions.Table) 313 if err != nil { 314 return err 315 } 316 defer db.Close() 317 318 return m.delete(db, key) 319 } 320 321 func (m *fileStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) { 322 var readOpts store.ReadOptions 323 for _, o := range opts { 324 o(&readOpts) 325 } 326 327 db, err := m.getDB(readOpts.Database, readOpts.Table) 328 if err != nil { 329 return nil, err 330 } 331 defer db.Close() 332 333 var keys []string 334 335 // Handle Prefix / suffix 336 if readOpts.Prefix || readOpts.Suffix { 337 prefix := "" 338 if readOpts.Prefix { 339 prefix = key 340 } 341 suffix := "" 342 if readOpts.Suffix { 343 suffix = key 344 } 345 // list the keys 346 keys = m.list(db, readOpts.Order, readOpts.Limit, readOpts.Offset, prefix, suffix) 347 } else { 348 keys = []string{key} 349 } 350 351 var results []*store.Record 352 353 for _, k := range keys { 354 r, err := m.get(db, k) 355 if err != nil { 356 return results, err 357 } 358 results = append(results, r) 359 } 360 361 return results, nil 362 } 363 364 func (m *fileStore) Write(r *store.Record, opts ...store.WriteOption) error { 365 var writeOpts store.WriteOptions 366 for _, o := range opts { 367 o(&writeOpts) 368 } 369 370 db, err := m.getDB(writeOpts.Database, writeOpts.Table) 371 if err != nil { 372 return err 373 } 374 defer db.Close() 375 376 if len(opts) > 0 { 377 // Copy the record before applying options, or the incoming record will be mutated 378 newRecord := store.Record{} 379 newRecord.Key = r.Key 380 newRecord.Value = r.Value 381 newRecord.Metadata = make(map[string]interface{}) 382 newRecord.Expiry = r.Expiry 383 384 for k, v := range r.Metadata { 385 newRecord.Metadata[k] = v 386 } 387 388 return m.set(db, &newRecord) 389 } 390 391 return m.set(db, r) 392 } 393 394 func (m *fileStore) Options() store.Options { 395 return m.options 396 } 397 398 func (m *fileStore) List(opts ...store.ListOption) ([]string, error) { 399 var listOptions store.ListOptions 400 401 for _, o := range opts { 402 o(&listOptions) 403 } 404 405 db, err := m.getDB(listOptions.Database, listOptions.Table) 406 if err != nil { 407 return nil, err 408 } 409 defer db.Close() 410 411 allKeys := m.list(db, listOptions.Order, listOptions.Limit, listOptions.Offset, listOptions.Prefix, listOptions.Suffix) 412 413 return allKeys, nil 414 } 415 416 func (m *fileStore) String() string { 417 return "file" 418 }