1
0

folderdb_local.go 7.3 KB

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