Browse Source

Update reset API to reflect new use cases.

/rest/reset clears the entire Syncthing DB and restart the program
/rest/reset&folder=default clears the indexes of the default folder
Lode Hoste 10 years ago
parent
commit
ab287ebf40
5 changed files with 222 additions and 24 deletions
  1. 19 4
      cmd/syncthing/gui.go
  2. 4 20
      cmd/syncthing/main.go
  3. 11 0
      internal/model/model.go
  4. 175 0
      test/reset_test.go
  5. 13 0
      test/syncthingprocess.go

+ 19 - 4
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()
 }
 

+ 4 - 20
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() {

+ 11 - 0
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)
 }

+ 175 - 0
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)
+}

+ 13 - 0
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 {