mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-01 10:29:08 -05:00
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
// Copyright (C) 2025 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 sqlite
|
|
|
|
import (
|
|
"cmp"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"iter"
|
|
"slices"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/syncthing/syncthing/internal/gen/bep"
|
|
"github.com/syncthing/syncthing/internal/gen/dbproto"
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// iterStructs returns an iterator over the given struct type by scanning
|
|
// the SQL rows. `rows` is closed when the iterator exits.
|
|
func iterStructs[T any](rows *sqlx.Rows, err error) (iter.Seq[T], func() error) {
|
|
if err != nil {
|
|
return func(_ func(T) bool) {}, func() error { return err }
|
|
}
|
|
|
|
var retErr error
|
|
return func(yield func(T) bool) {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
v := new(T)
|
|
if err := rows.StructScan(v); err != nil {
|
|
retErr = err
|
|
break
|
|
}
|
|
if cleanuper, ok := any(v).(interface{ cleanup() }); ok {
|
|
cleanuper.cleanup()
|
|
}
|
|
if !yield(*v) {
|
|
return
|
|
}
|
|
}
|
|
if err := rows.Err(); err != nil && retErr == nil {
|
|
retErr = err
|
|
}
|
|
}, func() error { return retErr }
|
|
}
|
|
|
|
// dbVector is a wrapper that allows protocol.Vector values to be serialized
|
|
// to and from the database.
|
|
type dbVector struct { //nolint:recvcheck
|
|
protocol.Vector
|
|
}
|
|
|
|
func (v dbVector) Value() (driver.Value, error) {
|
|
return v.String(), nil
|
|
}
|
|
|
|
func (v *dbVector) Scan(value any) error {
|
|
str, ok := value.(string)
|
|
if !ok {
|
|
return errors.New("not a string")
|
|
}
|
|
if str == "" {
|
|
v.Vector = protocol.Vector{}
|
|
return nil
|
|
}
|
|
vec, err := protocol.VectorFromString(str)
|
|
if err != nil {
|
|
return wrap(err)
|
|
}
|
|
|
|
// This is only necessary because I messed up counter serialisation and
|
|
// thereby ordering in 2.0.0 betas, and can be removed in the future.
|
|
slices.SortFunc(vec.Counters, func(a, b protocol.Counter) int { return cmp.Compare(a.ID, b.ID) })
|
|
|
|
v.Vector = vec
|
|
|
|
return nil
|
|
}
|
|
|
|
// indirectFI constructs a FileInfo from separate marshalled FileInfo and
|
|
// BlockList bytes.
|
|
type indirectFI struct {
|
|
Name string // not used, must be present as dest for Need iterator
|
|
FiProtobuf []byte
|
|
BlProtobuf []byte
|
|
Size int64 // not used
|
|
Modified int64 // not used
|
|
}
|
|
|
|
func (i indirectFI) FileInfo() (protocol.FileInfo, error) {
|
|
var fi bep.FileInfo
|
|
if err := proto.Unmarshal(i.FiProtobuf, &fi); err != nil {
|
|
return protocol.FileInfo{}, wrap(err, "unmarshal fileinfo")
|
|
}
|
|
if len(i.BlProtobuf) > 0 {
|
|
var bl dbproto.BlockList
|
|
if err := proto.Unmarshal(i.BlProtobuf, &bl); err != nil {
|
|
return protocol.FileInfo{}, wrap(err, "unmarshal blocklist")
|
|
}
|
|
fi.Blocks = bl.Blocks
|
|
}
|
|
fi.Name = osutil.NativeFilename(fi.Name)
|
|
return protocol.FileInfoFromDB(&fi), nil
|
|
}
|
|
|
|
func prefixEnd(s string) string {
|
|
if s == "" {
|
|
panic("bug: cannot represent end prefix for empty string")
|
|
}
|
|
bs := []byte(s)
|
|
for i := len(bs) - 1; i >= 0; i-- {
|
|
if bs[i] < 0xff {
|
|
bs[i]++
|
|
break
|
|
}
|
|
}
|
|
return string(bs)
|
|
}
|