util.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package sqlite
  7. import (
  8. "database/sql/driver"
  9. "errors"
  10. "iter"
  11. "github.com/jmoiron/sqlx"
  12. "github.com/syncthing/syncthing/internal/gen/bep"
  13. "github.com/syncthing/syncthing/internal/gen/dbproto"
  14. "github.com/syncthing/syncthing/lib/osutil"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. "google.golang.org/protobuf/proto"
  17. )
  18. // iterStructs returns an iterator over the given struct type by scanning
  19. // the SQL rows. `rows` is closed when the iterator exits.
  20. func iterStructs[T any](rows *sqlx.Rows, err error) (iter.Seq[T], func() error) {
  21. if err != nil {
  22. return func(_ func(T) bool) {}, func() error { return err }
  23. }
  24. var retErr error
  25. return func(yield func(T) bool) {
  26. defer rows.Close()
  27. for rows.Next() {
  28. v := new(T)
  29. if err := rows.StructScan(v); err != nil {
  30. retErr = err
  31. break
  32. }
  33. if cleanuper, ok := any(v).(interface{ cleanup() }); ok {
  34. cleanuper.cleanup()
  35. }
  36. if !yield(*v) {
  37. return
  38. }
  39. }
  40. if err := rows.Err(); err != nil && retErr == nil {
  41. retErr = err
  42. }
  43. }, func() error { return retErr }
  44. }
  45. // dbVector is a wrapper that allows protocol.Vector values to be serialized
  46. // to and from the database.
  47. type dbVector struct { //nolint:recvcheck
  48. protocol.Vector
  49. }
  50. func (v dbVector) Value() (driver.Value, error) {
  51. return v.String(), nil
  52. }
  53. func (v *dbVector) Scan(value any) error {
  54. str, ok := value.(string)
  55. if !ok {
  56. return errors.New("not a string")
  57. }
  58. if str == "" {
  59. v.Vector = protocol.Vector{}
  60. return nil
  61. }
  62. vec, err := protocol.VectorFromString(str)
  63. if err != nil {
  64. return wrap(err)
  65. }
  66. v.Vector = vec
  67. return nil
  68. }
  69. // indirectFI constructs a FileInfo from separate marshalled FileInfo and
  70. // BlockList bytes.
  71. type indirectFI struct {
  72. Name string // not used, must be present as dest for Need iterator
  73. FiProtobuf []byte
  74. BlProtobuf []byte
  75. Size int64 // not used
  76. Modified int64 // not used
  77. }
  78. func (i indirectFI) FileInfo() (protocol.FileInfo, error) {
  79. var fi bep.FileInfo
  80. if err := proto.Unmarshal(i.FiProtobuf, &fi); err != nil {
  81. return protocol.FileInfo{}, wrap(err, "unmarshal fileinfo")
  82. }
  83. if len(i.BlProtobuf) > 0 {
  84. var bl dbproto.BlockList
  85. if err := proto.Unmarshal(i.BlProtobuf, &bl); err != nil {
  86. return protocol.FileInfo{}, wrap(err, "unmarshal blocklist")
  87. }
  88. fi.Blocks = bl.Blocks
  89. }
  90. fi.Name = osutil.NativeFilename(fi.Name)
  91. return protocol.FileInfoFromDB(&fi), nil
  92. }
  93. func prefixEnd(s string) string {
  94. if s == "" {
  95. panic("bug: cannot represent end prefix for empty string")
  96. }
  97. bs := []byte(s)
  98. for i := len(bs) - 1; i >= 0; i-- {
  99. if bs[i] < 0xff {
  100. bs[i]++
  101. break
  102. }
  103. }
  104. return string(bs)
  105. }