db_update.go 2.7 KB

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