diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index f7aae3a14..de1111532 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -142,7 +142,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro postRestMux.HandleFunc("/rest/error/clear", restClearErrors) postRestMux.HandleFunc("/rest/ignores", withModel(m, restPostIgnores)) postRestMux.HandleFunc("/rest/model/override", withModel(m, restPostOverride)) - postRestMux.HandleFunc("/rest/reset", restPostReset) + postRestMux.HandleFunc("/rest/reset", withModel(m, restPostReset)) postRestMux.HandleFunc("/rest/restart", restPostRestart) postRestMux.HandleFunc("/rest/shutdown", restPostShutdown) postRestMux.HandleFunc("/rest/upgrade", restPostUpgrade) @@ -461,9 +461,24 @@ func restPostRestart(w http.ResponseWriter, r *http.Request) { go restart() } -func restPostReset(w http.ResponseWriter, r *http.Request) { - flushResponse(`{"ok": "resetting folders"}`, w) - resetFolders() +func restPostReset(m *model.Model, w http.ResponseWriter, r *http.Request) { + var qs = r.URL.Query() + folder := qs.Get("folder") + var err error + if len(folder) == 0 { + err = resetDB() + } else { + err = m.ResetFolder(folder) + } + if err != nil { + http.Error(w, err.Error(), 500) + return + } + if len(folder) == 0 { + flushResponse(`{"ok": "resetting database"}`, w) + } else { + flushResponse(`{"ok": "resetting folder " + folder}`, w) + } go restart() } diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index d8a54a3d6..bf009ab59 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -217,7 +217,7 @@ func main() { flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix") flag.BoolVar(&noBrowser, "no-browser", false, "Do not start browser") flag.BoolVar(&noRestart, "no-restart", noRestart, "Do not restart; just exit") - flag.BoolVar(&reset, "reset", false, "Prepare to resync from cluster") + flag.BoolVar(&reset, "reset", false, "Reset the database") flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade") flag.BoolVar(&doUpgradeCheck, "upgrade-check", false, "Check for available upgrade") flag.BoolVar(&showVersion, "version", false, "Show version") @@ -352,7 +352,7 @@ func main() { } if reset { - resetFolders() + resetDB() return } @@ -803,24 +803,8 @@ func renewUPnP(port int) { } } -func resetFolders() { - cfgFile := locations[locConfigFile] - cfg, err := config.Load(cfgFile, myID) - if err != nil { - log.Fatal(err) - } - - suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano()) - for _, folder := range cfg.Folders() { - if _, err := os.Stat(folder.Path); err == nil { - base := filepath.Base(folder.Path) - dir := filepath.Dir(folder.Path) - l.Infof("Reset: Moving %s -> %s", folder.Path, filepath.Join(dir, base+suffix)) - os.Rename(folder.Path, filepath.Join(dir, base+suffix)) - } - } - - os.RemoveAll(locations[locDatabase]) +func resetDB() error { + return os.RemoveAll(locations[locDatabase]) } func restart() { diff --git a/internal/model/model.go b/internal/model/model.go index e9f62e9b0..4fb25e90c 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -1590,6 +1590,17 @@ func (m *Model) CheckFolderHealth(id string) error { return err } +func (m *Model) ResetFolder(folder string) error { + for _, f := range db.ListFolders(m.db) { + if f == folder { + l.Infof("Cleaning data for folder %q", folder) + db.DropFolder(m.db, folder) + return nil + } + } + return fmt.Errorf("Unknown folder %q", folder) +} + func (m *Model) String() string { return fmt.Sprintf("model@%p", m) } diff --git a/test/reset_test.go b/test/reset_test.go new file mode 100644 index 000000000..d10fb5530 --- /dev/null +++ b/test/reset_test.go @@ -0,0 +1,175 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build integration + +package integration + +import ( + "log" + "os" + "path/filepath" + "testing" + "time" +) + +func TestReset(t *testing.T) { + // Clean and start a syncthing instance + + log.Println("Cleaning...") + err := removeAll("s1", "h1/index*") + if err != nil { + t.Fatal(err) + } + + p := syncthingProcess{ // id1 + instance: "1", + argv: []string{"-home", "h1"}, + port: 8081, + apiKey: apiKey, + } + err = p.start() + if err != nil { + t.Fatal(err) + } + defer p.stop() + + // Wait for one scan to succeed, or up to 20 seconds... This is to let + // startup, UPnP etc complete and make sure that we've performed folder + // error checking which creates the folder path if it's missing. + log.Println("Starting...") + waitForScan(t, &p) + + log.Println("Creating files...") + size := createFiles(t) + + log.Println("Scanning files...") + waitForScan(t, &p) + + m, err := p.model("default") + if err != nil { + t.Fatal(err) + } + expected := size + if m.LocalFiles != expected { + t.Fatalf("Incorrect number of files after initial scan, %d != %d", m.LocalFiles, expected) + } + + // Clear all files but restore the folder marker + log.Println("Cleaning...") + err = removeAll("s1/*", "h1/index*") + if err != nil { + t.Fatal(err) + } + os.Create("s1/.stfolder") + + // Reset indexes of an invalid folder + log.Println("Reset invalid folder") + err = p.reset("invalid") + if err == nil { + t.Fatalf("Cannot reset indexes of an invalid folder") + } + m, err = p.model("default") + if err != nil { + t.Fatal(err) + } + expected = size + if m.LocalFiles != expected { + t.Fatalf("Incorrect number of files after initial scan, %d != %d", m.LocalFiles, expected) + } + + // Reset indexes of the default folder + log.Println("Reset indexes of default folder") + err = p.reset("default") + if err != nil { + t.Fatal("Failed to reset indexes of the default folder:", err) + } + + // Wait for ST and scan + p.start() + waitForScan(t, &p) + + // Verify that we see them + m, err = p.model("default") + if err != nil { + t.Fatal(err) + } + expected = 0 + if m.LocalFiles != expected { + t.Fatalf("Incorrect number of files after initial scan, %d != %d", m.LocalFiles, expected) + } + + // Recreate the files and scan + log.Println("Creating files...") + size = createFiles(t) + waitForScan(t, &p) + + // Verify that we see them + m, err = p.model("default") + if err != nil { + t.Fatal(err) + } + expected = size + if m.LocalFiles != expected { + t.Fatalf("Incorrect number of files after second creation phase, %d != %d", m.LocalFiles, expected) + } + + // Reset all indexes + log.Println("Reset DB...") + err = p.reset("") + if err != nil { + t.Fatalf("Failed to reset indexes", err) + } + + // Wait for ST and scan + p.start() + waitForScan(t, &p) + + m, err = p.model("default") + if err != nil { + t.Fatal(err) + } + expected = size + if m.LocalFiles != expected { + t.Fatalf("Incorrect number of files after initial scan, %d != %d", m.LocalFiles, expected) + } +} + +func waitForScan(t *testing.T, p *syncthingProcess) { + // Wait for one scan to succeed, or up to 20 seconds... + for i := 0; i < 20; i++ { + err := p.rescan("default") + if err != nil { + time.Sleep(time.Second) + continue + } + break + } +} + +func createFiles(t *testing.T) int { + // Create eight empty files and directories + files := []string{"f1", "f2", "f3", "f4", "f11", "f12", "f13", "f14"} + dirs := []string{"d1", "d2", "d3", "d4", "d11", "d12", "d13", "d14"} + all := append(files, dirs...) + + for _, file := range files { + fd, err := os.Create(filepath.Join("s1", file)) + if err != nil { + t.Fatal(err) + } + fd.Close() + } + + for _, dir := range dirs { + err := os.Mkdir(filepath.Join("s1", dir), 0755) + if err != nil { + t.Fatal(err) + } + } + + return len(all) +} diff --git a/test/syncthingprocess.go b/test/syncthingprocess.go index f97ad1041..ba3e86520 100644 --- a/test/syncthingprocess.go +++ b/test/syncthingprocess.go @@ -310,6 +310,19 @@ func (p *syncthingProcess) rescan(folder string) error { return nil } +func (p *syncthingProcess) reset(folder string) error { + resp, err := p.post("/rest/reset?folder="+folder, nil) + if err != nil { + return err + } + data, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Reset %q: status code %d: %s", folder, resp.StatusCode, data) + } + return nil +} + func allDevicesInSync(p []syncthingProcess) error { for _, device := range p { if err := device.allPeersInSync(); err != nil {