| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- // Copyright (C) 2018 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package db
- import (
- "encoding/binary"
- "sort"
- "github.com/syncthing/syncthing/lib/db/backend"
- "github.com/syncthing/syncthing/lib/sync"
- )
- // A smallIndex is an in memory bidirectional []byte to uint32 map. It gives
- // fast lookups in both directions and persists to the database. Don't use for
- // storing more items than fit comfortably in RAM.
- type smallIndex struct {
- db backend.Backend
- prefix []byte
- id2val map[uint32]string
- val2id map[string]uint32
- nextID uint32
- mut sync.Mutex
- }
- func newSmallIndex(db backend.Backend, prefix []byte) *smallIndex {
- idx := &smallIndex{
- db: db,
- prefix: prefix,
- id2val: make(map[uint32]string),
- val2id: make(map[string]uint32),
- mut: sync.NewMutex(),
- }
- idx.load()
- return idx
- }
- // load iterates over the prefix space in the database and populates the in
- // memory maps.
- func (i *smallIndex) load() {
- it, err := i.db.NewPrefixIterator(i.prefix)
- if err != nil {
- panic("loading small index: " + err.Error())
- }
- defer it.Release()
- for it.Next() {
- val := string(it.Value())
- id := binary.BigEndian.Uint32(it.Key()[len(i.prefix):])
- if val != "" {
- // Empty value means the entry has been deleted.
- i.id2val[id] = val
- i.val2id[val] = id
- }
- if id >= i.nextID {
- i.nextID = id + 1
- }
- }
- }
- // ID returns the index number for the given byte slice, allocating a new one
- // and persisting this to the database if necessary.
- func (i *smallIndex) ID(val []byte) (uint32, error) {
- i.mut.Lock()
- // intentionally avoiding defer here as we want this call to be as fast as
- // possible in the general case (folder ID already exists). The map lookup
- // with the conversion of []byte to string is compiler optimized to not
- // copy the []byte, which is why we don't assign it to a temp variable
- // here.
- if id, ok := i.val2id[string(val)]; ok {
- i.mut.Unlock()
- return id, nil
- }
- id := i.nextID
- i.nextID++
- valStr := string(val)
- i.val2id[valStr] = id
- i.id2val[id] = valStr
- key := make([]byte, len(i.prefix)+8) // prefix plus uint32 id
- copy(key, i.prefix)
- binary.BigEndian.PutUint32(key[len(i.prefix):], id)
- if err := i.db.Put(key, val); err != nil {
- i.mut.Unlock()
- return 0, err
- }
- i.mut.Unlock()
- return id, nil
- }
- // Val returns the value for the given index number, or (nil, false) if there
- // is no such index number.
- func (i *smallIndex) Val(id uint32) ([]byte, bool) {
- i.mut.Lock()
- val, ok := i.id2val[id]
- i.mut.Unlock()
- if !ok {
- return nil, false
- }
- return []byte(val), true
- }
- func (i *smallIndex) Delete(val []byte) error {
- i.mut.Lock()
- defer i.mut.Unlock()
- // Check the reverse mapping to get the ID for the value.
- if id, ok := i.val2id[string(val)]; ok {
- // Generate the corresponding database key.
- key := make([]byte, len(i.prefix)+8) // prefix plus uint32 id
- copy(key, i.prefix)
- binary.BigEndian.PutUint32(key[len(i.prefix):], id)
- // Put an empty value into the database. This indicates that the
- // entry does not exist any more and prevents the ID from being
- // reused in the future.
- if err := i.db.Put(key, []byte{}); err != nil {
- return err
- }
- // Delete reverse mapping.
- delete(i.id2val, id)
- }
- // Delete forward mapping.
- delete(i.val2id, string(val))
- return nil
- }
- // Values returns the set of values in the index
- func (i *smallIndex) Values() []string {
- // In principle this method should return [][]byte because all the other
- // methods deal in []byte keys. However, in practice, where it's used
- // wants a []string and it's easier to just create that here rather than
- // having to convert both here and there...
- i.mut.Lock()
- vals := make([]string, 0, len(i.val2id))
- for val := range i.val2id {
- vals = append(vals, val)
- }
- i.mut.Unlock()
- sort.Strings(vals)
- return vals
- }
|