Ver código fonte

Merge pull request #1633 from calmh/errorstate

Move folder errors to state
Audrius Butkevicius 10 anos atrás
pai
commit
ba4a6fc0c5

+ 6 - 1
cmd/syncthing/gui.go

@@ -354,7 +354,12 @@ func folderSummary(m *model.Model, folder string) map[string]interface{} {
 
 	res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
 
-	res["state"], res["stateChanged"] = m.State(folder)
+	var err error
+	res["state"], res["stateChanged"], err = m.State(folder)
+	if err != nil {
+		res["error"] = err.Error()
+	}
+
 	res["version"] = m.CurrentLocalVersion(folder) + m.RemoteLocalVersion(folder)
 
 	ignorePatterns, _, _ := m.GetIgnores(folder)

+ 7 - 3
cmd/syncthing/main_test.go

@@ -42,6 +42,7 @@ func TestFolderErrors(t *testing.T) {
 
 	m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
 	m.AddFolder(fcfg)
+	m.StartFolderRW("folder")
 
 	if err := m.CheckFolderHealth("folder"); err != nil {
 		t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
@@ -69,6 +70,7 @@ func TestFolderErrors(t *testing.T) {
 
 	m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
 	m.AddFolder(fcfg)
+	m.StartFolderRW("folder")
 
 	if err := m.CheckFolderHealth("folder"); err != nil {
 		t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
@@ -90,8 +92,9 @@ func TestFolderErrors(t *testing.T) {
 
 	m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
 	m.AddFolder(fcfg)
+	m.StartFolderRW("folder")
 
-	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
+	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
 		t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
 	}
 
@@ -117,8 +120,9 @@ func TestFolderErrors(t *testing.T) {
 
 	m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
 	m.AddFolder(fcfg)
+	m.StartFolderRW("folder")
 
-	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder path missing" {
+	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder path missing" {
 		t.Error("Incorrect error: Folder path missing !=", m.CheckFolderHealth("folder"))
 	}
 
@@ -126,7 +130,7 @@ func TestFolderErrors(t *testing.T) {
 
 	os.Mkdir("testdata/testfolder", 0700)
 
-	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
+	if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
 		t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
 	}
 

+ 2 - 2
gui/index.html

@@ -193,9 +193,9 @@
                       <th><span class="glyphicon glyphicon-folder-open"></span>&emsp;<span translate>Folder Path</span></th>
                       <td class="text-right">{{folder.path}}</td>
                     </tr>
-                    <tr ng-if="model[folder.id].invalid">
+                    <tr ng-if="model[folder.id].invalid || model[folder.id].error">
                       <th><span class="glyphicon glyphicon-warning-sign"></span>&emsp;<span translate>Error</span></th>
-                      <td class="text-right">{{model[folder.id].invalid}}</td>
+                      <td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
                     </tr>
                     <tr>
                       <th><span class="glyphicon glyphicon-globe"></span>&emsp;<span translate>Global State</span></th>

+ 8 - 1
gui/scripts/syncthing/core/controllers/syncthingController.js

@@ -461,10 +461,14 @@ angular.module('syncthing.core')
                 return 'unshared';
             }
 
-            if ($scope.model[folderCfg.id].invalid !== '') {
+            if ($scope.model[folderCfg.id].invalid) {
                 return 'stopped';
             }
 
+            if ($scope.model[folderCfg.id].state == 'error') {
+                return 'stopped'; // legacy, the state is called "stopped" in the GUI
+            }
+
             return '' + $scope.model[folderCfg.id].state;
         };
 
@@ -494,6 +498,9 @@ angular.module('syncthing.core')
             if (state == 'scanning') {
                 return 'primary';
             }
+            if (state == 'error') {
+                return 'danger';
+            }
             return 'info';
         };
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
internal/auto/gui.files.go


+ 0 - 23
internal/config/wrapper.go

@@ -215,29 +215,6 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) {
 	w.replaces <- w.cfg.Copy()
 }
 
-// Sets the folder error state. Emits ConfigSaved to cause a GUI refresh.
-func (w *Wrapper) SetFolderError(id string, err error) {
-	w.mut.Lock()
-	defer w.mut.Unlock()
-
-	w.folderMap = nil
-
-	for i := range w.cfg.Folders {
-		if w.cfg.Folders[i].ID == id {
-			errstr := ""
-			if err != nil {
-				errstr = err.Error()
-			}
-			if errstr != w.cfg.Folders[i].Invalid {
-				w.cfg.Folders[i].Invalid = errstr
-				events.Default.Log(events.ConfigSaved, w.cfg)
-				w.replaces <- w.cfg.Copy()
-			}
-			return
-		}
-	}
-}
-
 // Returns whether or not connection attempts from the given device should be
 // silently ignored.
 func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {

+ 41 - 17
internal/model/folderstate.go

@@ -1,17 +1,8 @@
 // Copyright (C) 2015 The Syncthing Authors.
 //
-// This program is free software: you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program. If not, see <http://www.gnu.org/licenses/>.
+// 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/.
 
 package model
 
@@ -28,7 +19,7 @@ const (
 	FolderIdle folderState = iota
 	FolderScanning
 	FolderSyncing
-	FolderCleaning
+	FolderError
 )
 
 func (s folderState) String() string {
@@ -37,10 +28,10 @@ func (s folderState) String() string {
 		return "idle"
 	case FolderScanning:
 		return "scanning"
-	case FolderCleaning:
-		return "cleaning"
 	case FolderSyncing:
 		return "syncing"
+	case FolderError:
+		return "error"
 	default:
 		return "unknown"
 	}
@@ -51,10 +42,16 @@ type stateTracker struct {
 
 	mut     sync.Mutex
 	current folderState
+	err     error
 	changed time.Time
 }
 
+// setState sets the new folder state, for states other than FolderError.
 func (s *stateTracker) setState(newState folderState) {
+	if newState == FolderError {
+		panic("must use setError")
+	}
+
 	s.mut.Lock()
 	if newState != s.current {
 		/* This should hold later...
@@ -74,6 +71,7 @@ func (s *stateTracker) setState(newState folderState) {
 		}
 
 		s.current = newState
+		s.err = nil
 		s.changed = time.Now()
 
 		events.Default.Log(events.StateChanged, eventData)
@@ -81,9 +79,35 @@ func (s *stateTracker) setState(newState folderState) {
 	s.mut.Unlock()
 }
 
-func (s *stateTracker) getState() (current folderState, changed time.Time) {
+// getState returns the current state, the time when it last changed, and the
+// current error or nil.
+func (s *stateTracker) getState() (current folderState, changed time.Time, err error) {
 	s.mut.Lock()
-	current, changed = s.current, s.changed
+	current, changed, err = s.current, s.changed, s.err
 	s.mut.Unlock()
 	return
 }
+
+// setError sets the folder state to FolderError with the specified error.
+func (s *stateTracker) setError(err error) {
+	s.mut.Lock()
+	if s.current != FolderError || s.err.Error() != err.Error() {
+		eventData := map[string]interface{}{
+			"folder": s.folder,
+			"to":     FolderError.String(),
+			"from":   s.current.String(),
+			"error":  err.Error(),
+		}
+
+		if !s.changed.IsZero() {
+			eventData["duration"] = time.Since(s.changed).Seconds()
+		}
+
+		s.current = FolderError
+		s.err = err
+		s.changed = time.Now()
+
+		events.Default.Log(events.StateChanged, eventData)
+	}
+	s.mut.Unlock()
+}

+ 38 - 43
internal/model/model.go

@@ -48,8 +48,9 @@ type service interface {
 	Jobs() ([]string, []string) // In progress, Queued
 	BringToFront(string)
 
-	setState(folderState)
-	getState() (folderState, time.Time)
+	setState(state folderState)
+	setError(err error)
+	getState() (folderState, time.Time, error)
 }
 
 type Model struct {
@@ -1083,13 +1084,13 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
 
 func (m *Model) ScanFolders() map[string]error {
 	m.fmut.RLock()
-	var folders = make([]string, 0, len(m.folderCfgs))
+	folders := make([]string, 0, len(m.folderCfgs))
 	for folder := range m.folderCfgs {
 		folders = append(folders, folder)
 	}
 	m.fmut.RUnlock()
 
-	var errors = make(map[string]error, len(m.folderCfgs))
+	errors := make(map[string]error, len(m.folderCfgs))
 	var errorsMut sync.Mutex
 
 	var wg sync.WaitGroup
@@ -1102,11 +1103,15 @@ func (m *Model) ScanFolders() map[string]error {
 				errorsMut.Lock()
 				errors[folder] = err
 				errorsMut.Unlock()
+
 				// Potentially sets the error twice, once in the scanner just
 				// by doing a check, and once here, if the error returned is
 				// the same one as returned by CheckFolderHealth, though
-				// duplicate set is handled by SetFolderError
-				m.cfg.SetFolderError(folder, err)
+				// duplicate set is handled by setError.
+				m.fmut.RLock()
+				srv := m.folderRunners[folder]
+				m.fmut.RUnlock()
+				srv.setError(err)
 			}
 			wg.Done()
 		}()
@@ -1182,13 +1187,13 @@ nextSub:
 	}
 
 	runner.setState(FolderScanning)
-	defer runner.setState(FolderIdle)
-	fchan, err := w.Walk()
 
+	fchan, err := w.Walk()
 	if err != nil {
-		m.cfg.SetFolderError(folder, err)
+		runner.setError(err)
 		return err
 	}
+
 	batchSize := 100
 	batch := make([]protocol.FileInfo, 0, batchSize)
 	for f := range fchan {
@@ -1298,6 +1303,7 @@ nextSub:
 		fs.Update(protocol.LocalDeviceID, batch)
 	}
 
+	runner.setState(FolderIdle)
 	return nil
 }
 
@@ -1340,15 +1346,18 @@ func (m *Model) clusterConfig(device protocol.DeviceID) protocol.ClusterConfigMe
 	return cm
 }
 
-func (m *Model) State(folder string) (string, time.Time) {
+func (m *Model) State(folder string) (string, time.Time, error) {
 	m.fmut.RLock()
 	runner, ok := m.folderRunners[folder]
 	m.fmut.RUnlock()
 	if !ok {
-		return "", time.Time{}
+		// The returned error should be an actual folder error, so returning
+		// errors.New("does not exist") or similar here would be
+		// inappropriate.
+		return "", time.Time{}, nil
 	}
-	state, changed := runner.getState()
-	return state.String(), changed
+	state, changed, err := runner.getState()
+	return state.String(), changed, err
 }
 
 func (m *Model) Override(folder string) {
@@ -1528,7 +1537,7 @@ func (m *Model) BringToFront(folder, file string) {
 func (m *Model) CheckFolderHealth(id string) error {
 	folder, ok := m.cfg.Folders()[id]
 	if !ok {
-		return errors.New("Folder does not exist")
+		return errors.New("folder does not exist")
 	}
 
 	fi, err := os.Stat(folder.Path())
@@ -1538,9 +1547,9 @@ func (m *Model) CheckFolderHealth(id string) error {
 		// that all files have been deleted which might not be the case,
 		// so mark it as invalid instead.
 		if err != nil || !fi.IsDir() {
-			err = errors.New("Folder path missing")
+			err = errors.New("folder path missing")
 		} else if !folder.HasMarker() {
-			err = errors.New("Folder marker missing")
+			err = errors.New("folder marker missing")
 		}
 	} else if os.IsNotExist(err) {
 		// If we don't have any files in the index, and the directory
@@ -1555,35 +1564,21 @@ func (m *Model) CheckFolderHealth(id string) error {
 		err = folder.CreateMarker()
 	}
 
-	if err == nil {
-		if folder.Invalid != "" {
-			l.Infof("Starting folder %q after error %q", folder.ID, folder.Invalid)
-			m.cfg.SetFolderError(id, nil)
-		}
+	m.fmut.RLock()
+	runner := m.folderRunners[folder.ID]
+	m.fmut.RUnlock()
+	_, _, oldErr := runner.getState()
 
-		if folder, ok := m.cfg.Folders()[id]; !ok || folder.Invalid != "" {
-			panic("Unable to unset folder \"" + id + "\" error.")
+	if err != nil {
+		if oldErr != nil && oldErr.Error() != err.Error() {
+			l.Infof("Folder %q error changed: %q -> %q", folder.ID, oldErr, err)
+		} else if oldErr == nil {
+			l.Warnf("Stopping folder %q - %v", folder.ID, err)
 		}
-
-		return nil
-	}
-
-	if folder.Invalid == err.Error() {
-		return err
-	}
-
-	// folder is a copy of the original struct, hence Invalid value is
-	// preserved after the set.
-	m.cfg.SetFolderError(id, err)
-
-	if folder.Invalid == "" {
-		l.Warnf("Stopping folder %q - %v", folder.ID, err)
-	} else {
-		l.Infof("Folder %q error changed: %q -> %q", folder.ID, folder.Invalid, err)
-	}
-
-	if folder, ok := m.cfg.Folders()[id]; !ok || folder.Invalid != err.Error() {
-		panic("Unable to set folder \"" + id + "\" error.")
+		runner.setError(err)
+	} else if oldErr != nil {
+		l.Infof("Folder %q error is cleared, restarting", folder.ID)
+		runner.setState(FolderIdle)
 	}
 
 	return err

+ 18 - 10
internal/model/model_test.go

@@ -621,21 +621,25 @@ func TestROScanRecovery(t *testing.T) {
 			if time.Now().After(timeout) {
 				return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
 			}
-			if m.cfg.Folders()["default"].Invalid == status {
+			_, _, err := m.State("default")
+			if err == nil && status == "" {
+				return nil
+			}
+			if err != nil && err.Error() == status {
 				return nil
 			}
 			time.Sleep(10 * time.Millisecond)
 		}
 	}
 
-	if err := waitFor("Folder path missing"); err != nil {
+	if err := waitFor("folder path missing"); err != nil {
 		t.Error(err)
 		return
 	}
 
 	os.Mkdir(fcfg.RawPath, 0700)
 
-	if err := waitFor("Folder marker missing"); err != nil {
+	if err := waitFor("folder marker missing"); err != nil {
 		t.Error(err)
 		return
 	}
@@ -654,14 +658,14 @@ func TestROScanRecovery(t *testing.T) {
 
 	os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
 
-	if err := waitFor("Folder marker missing"); err != nil {
+	if err := waitFor("folder marker missing"); err != nil {
 		t.Error(err)
 		return
 	}
 
 	os.Remove(fcfg.RawPath)
 
-	if err := waitFor("Folder path missing"); err != nil {
+	if err := waitFor("folder path missing"); err != nil {
 		t.Error(err)
 		return
 	}
@@ -701,21 +705,25 @@ func TestRWScanRecovery(t *testing.T) {
 			if time.Now().After(timeout) {
 				return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
 			}
-			if m.cfg.Folders()["default"].Invalid == status {
+			_, _, err := m.State("default")
+			if err == nil && status == "" {
+				return nil
+			}
+			if err != nil && err.Error() == status {
 				return nil
 			}
 			time.Sleep(10 * time.Millisecond)
 		}
 	}
 
-	if err := waitFor("Folder path missing"); err != nil {
+	if err := waitFor("folder path missing"); err != nil {
 		t.Error(err)
 		return
 	}
 
 	os.Mkdir(fcfg.RawPath, 0700)
 
-	if err := waitFor("Folder marker missing"); err != nil {
+	if err := waitFor("folder marker missing"); err != nil {
 		t.Error(err)
 		return
 	}
@@ -734,14 +742,14 @@ func TestRWScanRecovery(t *testing.T) {
 
 	os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
 
-	if err := waitFor("Folder marker missing"); err != nil {
+	if err := waitFor("folder marker missing"); err != nil {
 		t.Error(err)
 		return
 	}
 
 	os.Remove(fcfg.RawPath)
 
-	if err := waitFor("Folder path missing"); err != nil {
+	if err := waitFor("folder path missing"); err != nil {
 		t.Error(err)
 		return
 	}

+ 2 - 2
internal/model/rofolder.go

@@ -67,8 +67,8 @@ func (s *roFolder) Serve() {
 				// Potentially sets the error twice, once in the scanner just
 				// by doing a check, and once here, if the error returned is
 				// the same one as returned by CheckFolderHealth, though
-				// duplicate set is handled by SetFolderError
-				s.model.cfg.SetFolderError(s.folder, err)
+				// duplicate set is handled by setError.
+				s.setError(err)
 				reschedule()
 				continue
 			}

+ 2 - 2
internal/model/rwfolder.go

@@ -245,8 +245,8 @@ func (p *rwFolder) Serve() {
 				// Potentially sets the error twice, once in the scanner just
 				// by doing a check, and once here, if the error returned is
 				// the same one as returned by CheckFolderHealth, though
-				// duplicate set is handled by SetFolderError
-				p.model.cfg.SetFolderError(p.folder, err)
+				// duplicate set is handled by setError.
+				p.setError(err)
 				rescheduleScan()
 				continue
 			}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff