fix(db): clean files for dropped folders at startup (#10280)
This adds a cleanup stage to remove database files for folders that no longer exist on startup. Folder database files were already removed when dropping a folder, assuming that the folder database had been opened at that point. This won't be the case though when a folder is removed from the config when Syncthing isn't running, or when a folder is dropped and re-migrated in a restarted migration.
This commit is contained in:
@@ -82,6 +82,10 @@ func Open(path string, opts ...Option) (*DB, error) {
|
|||||||
opt(db)
|
opt(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := db.cleanDroppedFolders(); err != nil {
|
||||||
|
slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +124,9 @@ func OpenForMigration(path string) (*DB, error) {
|
|||||||
folderDBOpener: openFolderDBForMigration,
|
folderDBOpener: openFolderDBForMigration,
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Touch device IDs that should always exist and have a low index
|
if err := db.cleanDroppedFolders(); err != nil {
|
||||||
// // numbers, and will never change
|
slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
|
||||||
// db.localDeviceIdx, _ = db.deviceIdxLocked(protocol.LocalDeviceID)
|
}
|
||||||
// db.tplInput["LocalDeviceIdx"] = db.localDeviceIdx
|
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,14 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/internal/slogutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *DB) DropFolder(folder string) error {
|
func (s *DB) DropFolder(folder string) error {
|
||||||
@@ -41,6 +46,37 @@ func (s *DB) ListFolders() ([]string, error) {
|
|||||||
return res, wrap(err)
|
return res, wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanDroppedFolders removes old database files for folders that no longer
|
||||||
|
// exist in the main database.
|
||||||
|
func (s *DB) cleanDroppedFolders() error {
|
||||||
|
// All expected folder databeses.
|
||||||
|
var names []string
|
||||||
|
err := s.stmt(`SELECT database_name FROM folders`).Select(&names)
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All folder database files on disk.
|
||||||
|
files, err := filepath.Glob(filepath.Join(s.pathBase, "folder.*"))
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any files that don't match a name in the database are removed.
|
||||||
|
for _, file := range files {
|
||||||
|
base := filepath.Base(file)
|
||||||
|
inDB := slices.ContainsFunc(names, func(name string) bool { return strings.HasPrefix(base, name) })
|
||||||
|
if !inDB {
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
slog.Warn("Failed to remove database file for old, dropped folder", slogutil.FilePath(base))
|
||||||
|
} else {
|
||||||
|
slog.Info("Cleaned out database file for old, dropped folder", slogutil.FilePath(base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// wrap returns the error wrapped with the calling function name and
|
// wrap returns the error wrapped with the calling function name and
|
||||||
// optional extra context strings as prefix. A nil error wraps to nil.
|
// optional extra context strings as prefix. A nil error wraps to nil.
|
||||||
func wrap(err error, context ...string) error {
|
func wrap(err error, context ...string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user