github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/search/doc_db.go (about) 1 // Copyright 2020 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package search 6 7 import ( 8 "context" 9 "io" 10 "sync" 11 12 "github.com/keybase/client/go/kbfs/ldbutils" 13 "github.com/keybase/client/go/kbfs/libfs" 14 "github.com/keybase/client/go/kbfs/libkbfs" 15 "github.com/keybase/client/go/logger" 16 "github.com/pkg/errors" 17 "github.com/syndtr/goleveldb/leveldb/storage" 18 billy "gopkg.in/src-d/go-billy.v4" 19 ) 20 21 // DocDb is a database that holds metadata about indexed documents. 22 type DocDb struct { 23 config libkbfs.Config 24 log logger.Logger 25 26 // Protect the DB from being shutdown while they're being 27 // accessed, and mutable data. 28 lock sync.RWMutex 29 db *ldbutils.LevelDb // docID -> doc metadata. 30 31 shutdownCh chan struct{} 32 33 closer func() 34 } 35 36 // newDocDbFromStorage creates a new *DocDb with the passed-in 37 // storage.Storage interface as the storage layer for the db. 38 func newDocDbFromStorage(config libkbfs.Config, s storage.Storage) ( 39 db *DocDb, err error) { 40 log := config.MakeLogger("DD") 41 closers := make([]io.Closer, 0, 1) 42 closer := func() { 43 for _, c := range closers { 44 closeErr := c.Close() 45 if closeErr != nil { 46 log.Warning("Error closing leveldb or storage: %+v", closeErr) 47 } 48 } 49 } 50 defer func() { 51 if err != nil { 52 err = errors.WithStack(err) 53 closer() 54 } 55 }() 56 57 dbOptions := ldbutils.LeveldbOptions(config.Mode()) 58 docDb, err := ldbutils.OpenLevelDbWithOptions(s, dbOptions) 59 if err != nil { 60 return nil, err 61 } 62 closers = append(closers, docDb) 63 64 return &DocDb{ 65 config: config, 66 db: docDb, 67 shutdownCh: make(chan struct{}), 68 closer: closer, 69 }, nil 70 } 71 72 // newDocDb creates a new *DocDb with a 73 // specified billy filesystem as the storage layer. 74 func newDocDb(config libkbfs.Config, fs billy.Filesystem) ( 75 db *DocDb, err error) { 76 log := config.MakeLogger("DD") 77 defer func() { 78 if err != nil { 79 log.Error("Error initializing doc db: %+v", err) 80 } 81 }() 82 83 s, err := libfs.OpenLevelDBStorage(fs, false) 84 if err != nil { 85 return nil, err 86 } 87 88 defer func() { 89 if err != nil { 90 s.Close() 91 } 92 }() 93 return newDocDbFromStorage(config, s) 94 } 95 96 type docMD struct { 97 // Exported only for serialization. 98 ParentDocID string `codec:"p"` 99 Name string `codec:"n"` 100 } 101 102 // getMetadataLocked retrieves the metadata for a doc in the db, or 103 // returns leveldb.ErrNotFound and a zero-valued metadata otherwise. 104 func (db *DocDb) getMetadataLocked(docID string) (md docMD, err error) { 105 mdBytes, err := db.db.Get([]byte(docID), nil) 106 if err != nil { 107 return docMD{}, err 108 } 109 err = db.config.Codec().Decode(mdBytes, &md) 110 if err != nil { 111 return docMD{}, err 112 } 113 return md, nil 114 } 115 116 // checkAndLockDb checks whether the db is started. 117 func (db *DocDb) checkDbLocked( 118 ctx context.Context, method string) error { 119 // First see if the context has expired since we began. 120 select { 121 case <-ctx.Done(): 122 return ctx.Err() 123 default: 124 } 125 126 // shutdownCh has to be checked under lock, otherwise we can race. 127 select { 128 case <-db.shutdownCh: 129 return errors.WithStack(DbClosedError{method}) 130 default: 131 } 132 if db.db == nil { 133 return errors.WithStack(DbClosedError{method}) 134 } 135 return nil 136 } 137 138 // Get returns the info for the given doc. 139 func (db *DocDb) Get(ctx context.Context, docID string) ( 140 parentDocID, name string, err error) { 141 db.lock.RLock() 142 defer db.lock.RUnlock() 143 err = db.checkDbLocked(ctx, "DD(Get)") 144 if err != nil { 145 return "", "", err 146 } 147 148 md, err := db.getMetadataLocked(docID) 149 if err != nil { 150 return "", "", err 151 } 152 return md.ParentDocID, md.Name, nil 153 } 154 155 // Put saves the revisions for the given TLF. 156 func (db *DocDb) Put( 157 ctx context.Context, docID, parentDocID, name string) error { 158 db.lock.Lock() 159 defer db.lock.Unlock() 160 err := db.checkDbLocked(ctx, "DD(Put)") 161 if err != nil { 162 return err 163 } 164 165 md := docMD{ 166 ParentDocID: parentDocID, 167 Name: name, 168 } 169 encodedMetadata, err := db.config.Codec().Encode(&md) 170 if err != nil { 171 return err 172 } 173 174 return db.db.Put([]byte(docID), encodedMetadata, nil) 175 } 176 177 // Delete removes the metadata for the TLF from the DB. 178 func (db *DocDb) Delete( 179 ctx context.Context, docID string) error { 180 db.lock.Lock() 181 defer db.lock.Unlock() 182 err := db.checkDbLocked(ctx, "DD(Delete)") 183 if err != nil { 184 return err 185 } 186 187 return db.db.Delete([]byte(docID), nil) 188 } 189 190 // Shutdown closes this db. 191 func (db *DocDb) Shutdown(ctx context.Context) { 192 db.lock.Lock() 193 defer db.lock.Unlock() 194 // shutdownCh has to be checked under lock, otherwise we can race. 195 select { 196 case <-db.shutdownCh: 197 db.log.CWarningf(ctx, "Shutdown called more than once") 198 return 199 default: 200 } 201 close(db.shutdownCh) 202 if db.db == nil { 203 return 204 } 205 db.closer() 206 db.db = nil 207 }