db_update.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. "errors"
  9. "fmt"
  10. "log/slog"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "slices"
  15. "strings"
  16. "github.com/syncthing/syncthing/internal/slogutil"
  17. )
  18. func (s *DB) DropFolder(folder string) error {
  19. s.folderDBsMut.Lock()
  20. defer s.folderDBsMut.Unlock()
  21. s.updateLock.Lock()
  22. defer s.updateLock.Unlock()
  23. _, err := s.stmt(`
  24. DELETE FROM folders
  25. WHERE folder_id = ?
  26. `).Exec(folder)
  27. if fdb, ok := s.folderDBs[folder]; ok {
  28. fdb.Close()
  29. _ = os.Remove(fdb.path)
  30. _ = os.Remove(fdb.path + "-wal")
  31. _ = os.Remove(fdb.path + "-shm")
  32. delete(s.folderDBs, folder)
  33. }
  34. return wrap(err)
  35. }
  36. func (s *DB) ListFolders() ([]string, error) {
  37. var res []string
  38. err := s.stmt(`
  39. SELECT folder_id FROM folders
  40. ORDER BY folder_id
  41. `).Select(&res)
  42. return res, wrap(err)
  43. }
  44. // cleanDroppedFolders removes old database files for folders that no longer
  45. // exist in the main database.
  46. func (s *DB) cleanDroppedFolders() error {
  47. // All expected folder databeses.
  48. var names []string
  49. err := s.stmt(`SELECT database_name FROM folders`).Select(&names)
  50. if err != nil {
  51. return wrap(err)
  52. }
  53. // All folder database files on disk.
  54. files, err := filepath.Glob(filepath.Join(s.pathBase, "folder.*"))
  55. if err != nil {
  56. return wrap(err)
  57. }
  58. // Any files that don't match a name in the database are removed.
  59. for _, file := range files {
  60. base := filepath.Base(file)
  61. inDB := slices.ContainsFunc(names, func(name string) bool { return strings.HasPrefix(base, name) })
  62. if !inDB {
  63. if err := os.Remove(file); err != nil {
  64. slog.Warn("Failed to remove database file for old, dropped folder", slogutil.FilePath(base))
  65. } else {
  66. slog.Info("Cleaned out database file for old, dropped folder", slogutil.FilePath(base))
  67. }
  68. }
  69. }
  70. return nil
  71. }
  72. // startFolderDatabases loads all existing folder databases, thus causing
  73. // migrations to apply.
  74. func (s *DB) startFolderDatabases() error {
  75. ids, err := s.ListFolders()
  76. if err != nil {
  77. return wrap(err)
  78. }
  79. for _, id := range ids {
  80. _, err := s.getFolderDB(id, false)
  81. if err != nil && !errors.Is(err, errNoSuchFolder) {
  82. return wrap(err)
  83. }
  84. }
  85. return nil
  86. }
  87. // wrap returns the error wrapped with the calling function name and
  88. // optional extra context strings as prefix. A nil error wraps to nil.
  89. func wrap(err error, context ...string) error {
  90. if err == nil {
  91. return nil
  92. }
  93. prefix := "error"
  94. pc, _, _, ok := runtime.Caller(1)
  95. details := runtime.FuncForPC(pc)
  96. if ok && details != nil {
  97. prefix = strings.ToLower(details.Name())
  98. if dotIdx := strings.LastIndex(prefix, "."); dotIdx > 0 {
  99. prefix = prefix[dotIdx+1:]
  100. }
  101. }
  102. if len(context) > 0 {
  103. for i := range context {
  104. context[i] = strings.TrimSpace(context[i])
  105. }
  106. extra := strings.Join(context, ", ")
  107. return fmt.Errorf("%s (%s): %w", prefix, extra, err)
  108. }
  109. return fmt.Errorf("%s: %w", prefix, err)
  110. }