Switch the database from LevelDB to SQLite, for greater stability and simpler code. Co-authored-by: Tommy van der Vorst <tommy@pixelspark.nl> Co-authored-by: bt90 <btom1990@googlemail.com>
194 lines
4.5 KiB
Go
194 lines
4.5 KiB
Go
// Copyright (C) 2014 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 olddb
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/syncthing/syncthing/internal/db/olddb/backend"
|
|
"github.com/syncthing/syncthing/internal/gen/bep"
|
|
"github.com/syncthing/syncthing/internal/gen/dbproto"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
)
|
|
|
|
// A readOnlyTransaction represents a database snapshot.
|
|
type readOnlyTransaction struct {
|
|
backend.ReadTransaction
|
|
keyer keyer
|
|
}
|
|
|
|
func (db *deprecatedLowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) {
|
|
tran, err := db.NewReadTransaction()
|
|
if err != nil {
|
|
return readOnlyTransaction{}, err
|
|
}
|
|
return db.readOnlyTransactionFromBackendTransaction(tran), nil
|
|
}
|
|
|
|
func (db *deprecatedLowlevel) readOnlyTransactionFromBackendTransaction(tran backend.ReadTransaction) readOnlyTransaction {
|
|
return readOnlyTransaction{
|
|
ReadTransaction: tran,
|
|
keyer: db.keyer,
|
|
}
|
|
}
|
|
|
|
func (t readOnlyTransaction) close() {
|
|
t.Release()
|
|
}
|
|
|
|
func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool, error) {
|
|
f, ok, err := t.getFileTrunc(key, false)
|
|
if err != nil || !ok {
|
|
return protocol.FileInfo{}, false, err
|
|
}
|
|
return f, true, nil
|
|
}
|
|
|
|
func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (protocol.FileInfo, bool, error) {
|
|
bs, err := t.Get(key)
|
|
if backend.IsNotFound(err) {
|
|
return protocol.FileInfo{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return protocol.FileInfo{}, false, err
|
|
}
|
|
f, err := t.unmarshalTrunc(bs, trunc)
|
|
if backend.IsNotFound(err) {
|
|
return protocol.FileInfo{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return protocol.FileInfo{}, false, err
|
|
}
|
|
return f, true, nil
|
|
}
|
|
|
|
func (t readOnlyTransaction) unmarshalTrunc(bs []byte, trunc bool) (protocol.FileInfo, error) {
|
|
if trunc {
|
|
var bfi dbproto.FileInfoTruncated
|
|
err := proto.Unmarshal(bs, &bfi)
|
|
if err != nil {
|
|
return protocol.FileInfo{}, err
|
|
}
|
|
if err := t.fillTruncated(&bfi); err != nil {
|
|
return protocol.FileInfo{}, err
|
|
}
|
|
return protocol.FileInfoFromDBTruncated(&bfi), nil
|
|
}
|
|
|
|
var bfi bep.FileInfo
|
|
err := proto.Unmarshal(bs, &bfi)
|
|
if err != nil {
|
|
return protocol.FileInfo{}, err
|
|
}
|
|
if err := t.fillFileInfo(&bfi); err != nil {
|
|
return protocol.FileInfo{}, err
|
|
}
|
|
return protocol.FileInfoFromDB(&bfi), nil
|
|
}
|
|
|
|
type blocksIndirectionError struct {
|
|
err error
|
|
}
|
|
|
|
func (e *blocksIndirectionError) Error() string {
|
|
return fmt.Sprintf("filling Blocks: %v", e.err)
|
|
}
|
|
|
|
func (e *blocksIndirectionError) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
// fillFileInfo follows the (possible) indirection of blocks and version
|
|
// vector and fills it out.
|
|
func (t readOnlyTransaction) fillFileInfo(fi *bep.FileInfo) error {
|
|
var key []byte
|
|
|
|
if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 {
|
|
// The blocks list is indirected and we need to load it.
|
|
key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash)
|
|
bs, err := t.Get(key)
|
|
if err != nil {
|
|
return &blocksIndirectionError{err}
|
|
}
|
|
var bl dbproto.BlockList
|
|
if err := proto.Unmarshal(bs, &bl); err != nil {
|
|
return err
|
|
}
|
|
fi.Blocks = bl.Blocks
|
|
}
|
|
|
|
if len(fi.VersionHash) != 0 {
|
|
key = t.keyer.GenerateVersionKey(key, fi.VersionHash)
|
|
bs, err := t.Get(key)
|
|
if err != nil {
|
|
return fmt.Errorf("filling Version: %w", err)
|
|
}
|
|
var v bep.Vector
|
|
if err := proto.Unmarshal(bs, &v); err != nil {
|
|
return err
|
|
}
|
|
fi.Version = &v
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// fillTruncated follows the (possible) indirection of version vector and
|
|
// fills it.
|
|
func (t readOnlyTransaction) fillTruncated(fi *dbproto.FileInfoTruncated) error {
|
|
var key []byte
|
|
|
|
if len(fi.VersionHash) == 0 {
|
|
return nil
|
|
}
|
|
|
|
key = t.keyer.GenerateVersionKey(key, fi.VersionHash)
|
|
bs, err := t.Get(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var v bep.Vector
|
|
if err := proto.Unmarshal(bs, &v); err != nil {
|
|
return err
|
|
}
|
|
fi.Version = &v
|
|
return nil
|
|
}
|
|
|
|
func (t *readOnlyTransaction) withHaveSequence(folder []byte, startSeq int64, fn Iterator) error {
|
|
first, err := t.keyer.GenerateSequenceKey(nil, folder, startSeq)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
last, err := t.keyer.GenerateSequenceKey(nil, folder, maxInt64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbi, err := t.NewRangeIterator(first, last)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dbi.Release()
|
|
|
|
for dbi.Next() {
|
|
f, ok, err := t.getFileByKey(dbi.Value())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if !fn(f) {
|
|
return nil
|
|
}
|
|
}
|
|
return dbi.Error()
|
|
}
|