folderdb_local.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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"
  9. "encoding/base64"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "iter"
  14. "strings"
  15. "text/tabwriter"
  16. "time"
  17. "github.com/syncthing/syncthing/internal/db"
  18. "github.com/syncthing/syncthing/internal/itererr"
  19. "github.com/syncthing/syncthing/lib/osutil"
  20. "github.com/syncthing/syncthing/lib/protocol"
  21. )
  22. func (s *folderDB) GetDeviceFile(device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) {
  23. file = osutil.NormalizedFilename(file)
  24. var ind indirectFI
  25. err := s.stmt(`
  26. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  27. INNER JOIN files f on fi.sequence = f.sequence
  28. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  29. INNER JOIN devices d ON f.device_idx = d.idx
  30. WHERE d.device_id = ? AND f.name = ?
  31. `).Get(&ind, device.String(), file)
  32. if errors.Is(err, sql.ErrNoRows) {
  33. return protocol.FileInfo{}, false, nil
  34. }
  35. if err != nil {
  36. return protocol.FileInfo{}, false, wrap(err)
  37. }
  38. fi, err := ind.FileInfo()
  39. if err != nil {
  40. return protocol.FileInfo{}, false, wrap(err, "indirect")
  41. }
  42. return fi, true, nil
  43. }
  44. func (s *folderDB) AllLocalFiles(device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
  45. it, errFn := iterStructs[indirectFI](s.stmt(`
  46. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  47. INNER JOIN files f on fi.sequence = f.sequence
  48. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  49. INNER JOIN devices d ON d.idx = f.device_idx
  50. WHERE d.device_id = ?
  51. `).Queryx(device.String()))
  52. return itererr.Map(it, errFn, indirectFI.FileInfo)
  53. }
  54. func (s *folderDB) AllLocalFilesBySequence(device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) {
  55. var limitStr string
  56. if limit > 0 {
  57. limitStr = fmt.Sprintf(" LIMIT %d", limit)
  58. }
  59. it, errFn := iterStructs[indirectFI](s.stmt(`
  60. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  61. INNER JOIN files f on fi.sequence = f.sequence
  62. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  63. INNER JOIN devices d ON d.idx = f.device_idx
  64. WHERE d.device_id = ? AND f.sequence >= ?
  65. ORDER BY f.sequence`+limitStr).Queryx(
  66. device.String(), startSeq))
  67. return itererr.Map(it, errFn, indirectFI.FileInfo)
  68. }
  69. func (s *folderDB) AllLocalFilesWithPrefix(device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) {
  70. if prefix == "" {
  71. return s.AllLocalFiles(device)
  72. }
  73. prefix = osutil.NormalizedFilename(prefix)
  74. end := prefixEnd(prefix)
  75. it, errFn := iterStructs[indirectFI](s.sql.Queryx(`
  76. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  77. INNER JOIN files f on fi.sequence = f.sequence
  78. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  79. INNER JOIN devices d ON d.idx = f.device_idx
  80. WHERE d.device_id = ? AND f.name >= ? AND f.name < ?
  81. `, device.String(), prefix, end))
  82. return itererr.Map(it, errFn, indirectFI.FileInfo)
  83. }
  84. func (s *folderDB) AllLocalFilesWithBlocksHash(h []byte) (iter.Seq[db.FileMetadata], func() error) {
  85. return iterStructs[db.FileMetadata](s.stmt(`
  86. SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
  87. WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
  88. `).Queryx(h))
  89. }
  90. func (s *folderDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) {
  91. // We involve the files table in this select because deletion of blocks
  92. // & blocklists is deferred (garbage collected) while the files list is
  93. // not. This filters out blocks that are in fact deleted.
  94. return iterStructs[db.BlockMapEntry](s.stmt(`
  95. SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size, f.name as filename FROM files f
  96. LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash
  97. WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ?
  98. `).Queryx(hash))
  99. }
  100. func (s *folderDB) ListDevicesForFolder() ([]protocol.DeviceID, error) {
  101. var res []string
  102. err := s.stmt(`
  103. SELECT DISTINCT d.device_id FROM counts s
  104. INNER JOIN devices d ON d.idx = s.device_idx
  105. WHERE s.count > 0 AND s.device_idx != {{.LocalDeviceIdx}}
  106. ORDER BY d.device_id
  107. `).Select(&res)
  108. if err != nil {
  109. return nil, wrap(err)
  110. }
  111. devs := make([]protocol.DeviceID, len(res))
  112. for i, s := range res {
  113. devs[i], err = protocol.DeviceIDFromString(s)
  114. if err != nil {
  115. return nil, wrap(err)
  116. }
  117. }
  118. return devs, nil
  119. }
  120. func (s *folderDB) DebugCounts(out io.Writer) error {
  121. type deviceCountsRow struct {
  122. countsRow
  123. DeviceID string
  124. }
  125. delMap := map[bool]string{
  126. true: "del",
  127. false: "---",
  128. }
  129. var res []deviceCountsRow
  130. if err := s.stmt(`
  131. SELECT d.device_id as deviceid, s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s
  132. INNER JOIN devices d ON d.idx = s.device_idx
  133. `).Select(&res); err != nil {
  134. return wrap(err)
  135. }
  136. tw := tabwriter.NewWriter(out, 2, 2, 2, ' ', 0)
  137. fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", "DEVICE", "TYPE", "FLAGS", "DELETED", "COUNT", "SIZE")
  138. for _, row := range res {
  139. fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%d\t%d\n", shortDevice(row.DeviceID), shortType(row.Type), row.LocalFlags.HumanString(), delMap[row.Deleted], row.Count, row.Size)
  140. }
  141. return tw.Flush()
  142. }
  143. func (s *folderDB) DebugFilePattern(out io.Writer, name string) error {
  144. type hashFileMetadata struct {
  145. db.FileMetadata
  146. Version dbVector
  147. BlocklistHash []byte
  148. DeviceID string
  149. }
  150. name = "%" + name + "%"
  151. res := itererr.Zip(iterStructs[hashFileMetadata](s.stmt(`
  152. SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags, f.version, f.blocklist_hash as blocklisthash, d.device_id as deviceid FROM files f
  153. INNER JOIN devices d ON d.idx = f.device_idx
  154. WHERE f.name LIKE ?
  155. ORDER BY f.name, f.device_idx
  156. `).Queryx(name)))
  157. delMap := map[bool]string{
  158. true: "del",
  159. false: "---",
  160. }
  161. tw := tabwriter.NewWriter(out, 2, 2, 2, ' ', 0)
  162. fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "DEVICE", "TYPE", "NAME", "SEQUENCE", "DELETED", "MODIFIED", "SIZE", "FLAGS", "VERSION", "BLOCKLIST")
  163. for row, err := range res {
  164. if err != nil {
  165. return err
  166. }
  167. fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t%s\t%s\t%d\t%s\t%s\t%s\n", shortDevice(row.DeviceID), shortType(row.Type), row.Name, row.Sequence, delMap[row.Deleted], row.ModTime().UTC().Format(time.RFC3339Nano), row.Size, row.LocalFlags.HumanString(), row.Version.HumanString(), shortHash(row.BlocklistHash))
  168. }
  169. return tw.Flush()
  170. }
  171. func shortDevice(s string) string {
  172. if dev, err := protocol.DeviceIDFromString(s); err == nil && dev == protocol.LocalDeviceID {
  173. return "-local-"
  174. }
  175. short, _, _ := strings.Cut(s, "-")
  176. return short
  177. }
  178. func shortType(t protocol.FileInfoType) string {
  179. return strings.TrimPrefix(t.String(), "FILE_INFO_TYPE_")
  180. }
  181. func shortHash(bs []byte) string {
  182. if len(bs) == 0 {
  183. return "-nil-"
  184. }
  185. return base64.RawStdEncoding.EncodeToString(bs)[:8]
  186. }