Sfoglia il codice sorgente

lib/model: Improve remoteFolderState reporting (fixes #8266) (#8283)

André Colomb 3 anni fa
parent
commit
1eda82b95f

+ 1 - 0
gui/default/syncthing/core/eventService.js

@@ -64,6 +64,7 @@ angular.module('syncthing.core')
             PENDING_DEVICES_CHANGED: 'PendingDevicesChanged',   // Emitted when pending devices were added / updated (connection from unknown ID) or removed (device is ignored or added)
             DEVICE_PAUSED: 'DevicePaused',   // Emitted when a device has been paused
             DEVICE_RESUMED: 'DeviceResumed',   // Emitted when a device has been resumed
+            CLUSTER_CONFIG_RECEIVED: 'ClusterConfigReceived',   // Emitted when receiving a remote device's cluster config
             DOWNLOAD_PROGRESS: 'DownloadProgress',   // Emitted during file downloads for each folder for each file
             FAILURE: 'Failure',   // Specific errors sent to the usage reporting server for diagnosis
             FOLDER_COMPLETION: 'FolderCompletion',   //Emitted when the local or remote contents for a folder changes

+ 4 - 4
gui/default/syncthing/core/syncthingController.js

@@ -2368,7 +2368,7 @@ angular.module('syncthing.core')
         $scope.deviceNameMarkUnaccepted = function (deviceID, folderID) {
             var name = $scope.deviceName($scope.devices[deviceID]);
             // Add footnote if sharing was not accepted on the remote device
-            if (deviceID in $scope.completion && folderID in $scope.completion[deviceID] && !$scope.completion[deviceID][folderID].accepted) {
+            if (deviceID in $scope.completion && folderID in $scope.completion[deviceID] && $scope.completion[deviceID][folderID].remoteState == 'notSharing') {
                 name += '<sup>1</sup>';
             }
             return name;
@@ -2389,7 +2389,7 @@ angular.module('syncthing.core')
             for (var deviceID in $scope.completion) {
                 if (deviceID in $scope.devices
                     && folderCfg.id in $scope.completion[deviceID]
-                    && !$scope.completion[deviceID][folderCfg.id].accepted) {
+                    && $scope.completion[deviceID][folderCfg.id].remoteState == 'notSharing') {
                     return true;
                 }
             }
@@ -2420,7 +2420,7 @@ angular.module('syncthing.core')
         $scope.folderLabelMarkUnaccepted = function (folderID, deviceID) {
             var label = $scope.folderLabel(folderID);
             // Add footnote if sharing was not accepted on the remote device
-            if (deviceID in $scope.completion && folderID in $scope.completion[deviceID] && !$scope.completion[deviceID][folderID].accepted) {
+            if (deviceID in $scope.completion && folderID in $scope.completion[deviceID] && $scope.completion[deviceID][folderID].remoteState == 'notSharing') {
                 label += '<sup>1</sup>';
             }
             return label;
@@ -2440,7 +2440,7 @@ angular.module('syncthing.core')
             }
             for (var folderID in $scope.completion[deviceCfg.deviceID]) {
                 if (folderID in $scope.folders
-                    && !$scope.completion[deviceCfg.deviceID][folderID].accepted) {
+                    && $scope.completion[deviceCfg.deviceID][folderID].remoteState == 'notSharing') {
                     return true;
                 }
             }

+ 5 - 0
lib/events/events.go

@@ -35,6 +35,7 @@ const (
 	PendingDevicesChanged
 	DevicePaused
 	DeviceResumed
+	ClusterConfigReceived
 	LocalChangeDetected
 	RemoteChangeDetected
 	LocalIndexUpdated
@@ -118,6 +119,8 @@ func (t EventType) String() string {
 		return "DevicePaused"
 	case DeviceResumed:
 		return "DeviceResumed"
+	case ClusterConfigReceived:
+		return "ClusterConfigReceived"
 	case FolderScanProgress:
 		return "FolderScanProgress"
 	case FolderPaused:
@@ -203,6 +206,8 @@ func UnmarshalEventType(s string) EventType {
 		return DevicePaused
 	case "DeviceResumed":
 		return DeviceResumed
+	case "ClusterConfigReceived":
+		return ClusterConfigReceived
 	case "FolderScanProgress":
 		return FolderScanProgress
 	case "FolderPaused":

+ 10 - 4
lib/model/folder_summary.go

@@ -221,7 +221,7 @@ func (c *folderSummaryService) OnEventRequest() {
 // listenForUpdates subscribes to the event bus and makes note of folders that
 // need their data recalculated.
 func (c *folderSummaryService) listenForUpdates(ctx context.Context) error {
-	sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
+	sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.ClusterConfigReceived | events.FolderWatchStateChanged | events.DownloadProgress)
 	defer sub.Unsubscribe()
 
 	for {
@@ -244,12 +244,18 @@ func (c *folderSummaryService) processUpdate(ev events.Event) {
 	var folder string
 
 	switch ev.Type {
-	case events.DeviceConnected:
+	case events.DeviceConnected, events.ClusterConfigReceived:
 		// When a device connects we schedule a refresh of all
 		// folders shared with that device.
 
-		data := ev.Data.(map[string]string)
-		deviceID, _ := protocol.DeviceIDFromString(data["id"])
+		var deviceID protocol.DeviceID
+		if ev.Type == events.DeviceConnected {
+			data := ev.Data.(map[string]string)
+			deviceID, _ = protocol.DeviceIDFromString(data["id"])
+		} else {
+			data := ev.Data.(ClusterConfigReceivedEventData)
+			deviceID = data.Device
+		}
 
 		c.foldersMut.Lock()
 	nextFolder:

+ 23 - 3
lib/model/folderstate.go

@@ -55,11 +55,31 @@ func (s folderState) String() string {
 type remoteFolderState int
 
 const (
-	remoteValid remoteFolderState = iota
-	remoteNotSharing
-	remotePaused
+	remoteFolderUnknown remoteFolderState = iota
+	remoteFolderNotSharing
+	remoteFolderPaused
+	remoteFolderValid
 )
 
+func (s remoteFolderState) String() string {
+	switch s {
+	case remoteFolderUnknown:
+		return "unknown"
+	case remoteFolderNotSharing:
+		return "notSharing"
+	case remoteFolderPaused:
+		return "paused"
+	case remoteFolderValid:
+		return "valid"
+	default:
+		return "unknown"
+	}
+}
+
+func (s remoteFolderState) MarshalText() ([]byte, error) {
+	return []byte(s.String()), nil
+}
+
 type stateTracker struct {
 	folderID string
 	evLogger events.Logger

+ 20 - 12
lib/model/model.go

@@ -800,10 +800,10 @@ type FolderCompletion struct {
 	NeedItems     int
 	NeedDeletes   int
 	Sequence      int64
-	Accepted      bool
+	RemoteState   remoteFolderState
 }
 
-func newFolderCompletion(global, need db.Counts, sequence int64, accepted bool) FolderCompletion {
+func newFolderCompletion(global, need db.Counts, sequence int64, state remoteFolderState) FolderCompletion {
 	comp := FolderCompletion{
 		GlobalBytes: global.Bytes,
 		NeedBytes:   need.Bytes,
@@ -811,7 +811,7 @@ func newFolderCompletion(global, need db.Counts, sequence int64, accepted bool)
 		NeedItems:   need.Files + need.Directories + need.Symlinks,
 		NeedDeletes: need.Deleted,
 		Sequence:    sequence,
-		Accepted:    accepted,
+		RemoteState: state,
 	}
 	comp.setComplectionPct()
 	return comp
@@ -843,7 +843,7 @@ func (comp *FolderCompletion) setComplectionPct() {
 	}
 }
 
-// Map returns the members as a map, e.g. used in api to serialize as Json.
+// Map returns the members as a map, e.g. used in api to serialize as JSON.
 func (comp FolderCompletion) Map() map[string]interface{} {
 	return map[string]interface{}{
 		"completion":  comp.CompletionPct,
@@ -853,7 +853,7 @@ func (comp FolderCompletion) Map() map[string]interface{} {
 		"needItems":   comp.NeedItems,
 		"needDeletes": comp.NeedDeletes,
 		"sequence":    comp.Sequence,
-		"accepted":    comp.Accepted,
+		"remoteState": comp.RemoteState,
 	}
 }
 
@@ -904,7 +904,7 @@ func (m *model) folderCompletion(device protocol.DeviceID, folder string) (Folde
 	defer snap.Release()
 
 	m.pmut.RLock()
-	accepted := m.remoteFolderStates[device][folder] != remoteNotSharing
+	state := m.remoteFolderStates[device][folder]
 	downloaded := m.deviceDownloads[device].BytesDownloaded(folder)
 	m.pmut.RUnlock()
 
@@ -915,7 +915,7 @@ func (m *model) folderCompletion(device protocol.DeviceID, folder string) (Folde
 		need.Bytes = 0
 	}
 
-	comp := newFolderCompletion(snap.GlobalSize(), need, snap.Sequence(device), accepted)
+	comp := newFolderCompletion(snap.GlobalSize(), need, snap.Sequence(device), state)
 
 	l.Debugf("%v Completion(%s, %q): %v", m, device, folder, comp.Map())
 	return comp, nil
@@ -1147,6 +1147,10 @@ type clusterConfigDeviceInfo struct {
 	local, remote protocol.Device
 }
 
+type ClusterConfigReceivedEventData struct {
+	Device protocol.DeviceID `json:"device"`
+}
+
 func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) error {
 	// Check the peer device's announced folders against our own. Emits events
 	// for folders that we don't expect (unknown or not shared).
@@ -1234,6 +1238,10 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
 	m.remoteFolderStates[deviceID] = states
 	m.pmut.Unlock()
 
+	m.evLogger.Log(events.ClusterConfigReceived, ClusterConfigReceivedEventData{
+		Device: deviceID,
+	})
+
 	if len(tempIndexFolders) > 0 {
 		m.pmut.RLock()
 		conn, ok := m.conn[deviceID]
@@ -1278,7 +1286,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
 	}
 	of := db.ObservedFolder{Time: time.Now().Truncate(time.Second)}
 	for _, folder := range folders {
-		seenFolders[folder.ID] = remoteValid
+		seenFolders[folder.ID] = remoteFolderValid
 
 		cfg, ok := m.cfg.Folder(folder.ID)
 		if ok {
@@ -1319,7 +1327,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
 
 		if folder.Paused {
 			indexHandlers.Remove(folder.ID)
-			seenFolders[cfg.ID] = remotePaused
+			seenFolders[cfg.ID] = remoteFolderPaused
 			continue
 		}
 
@@ -1374,8 +1382,8 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
 	// Explicitly mark folders we offer, but the remote has not accepted
 	for folderID, cfg := range m.cfg.Folders() {
 		if _, seen := seenFolders[folderID]; !seen && cfg.SharedWith(deviceID) {
-			l.Debugf("Remote device %v has not accepted folder %s", deviceID.Short(), cfg.Description())
-			seenFolders[folderID] = remoteNotSharing
+			l.Debugf("Remote device %v has not accepted sharing folder %s", deviceID.Short(), cfg.Description())
+			seenFolders[folderID] = remoteFolderNotSharing
 		}
 	}
 
@@ -2712,7 +2720,7 @@ func (m *model) availabilityInSnapshotPRlocked(cfg config.FolderConfiguration, s
 		if _, ok := m.remoteFolderStates[device]; !ok {
 			continue
 		}
-		if state, ok := m.remoteFolderStates[device][cfg.ID]; !ok || state == remotePaused {
+		if state := m.remoteFolderStates[device][cfg.ID]; state != remoteFolderValid {
 			continue
 		}
 		_, ok := m.conn[device]

+ 5 - 5
lib/model/model_test.go

@@ -3764,16 +3764,16 @@ func TestClusterConfigOnFolderUnpause(t *testing.T) {
 
 func TestAddFolderCompletion(t *testing.T) {
 	// Empty folders are always 100% complete.
-	comp := newFolderCompletion(db.Counts{}, db.Counts{}, 0, true)
-	comp.add(newFolderCompletion(db.Counts{}, db.Counts{}, 0, false))
+	comp := newFolderCompletion(db.Counts{}, db.Counts{}, 0, remoteFolderValid)
+	comp.add(newFolderCompletion(db.Counts{}, db.Counts{}, 0, remoteFolderPaused))
 	if comp.CompletionPct != 100 {
 		t.Error(comp.CompletionPct)
 	}
 
 	// Completion is of the whole
-	comp = newFolderCompletion(db.Counts{Bytes: 100}, db.Counts{}, 0, true)             // 100% complete
-	comp.add(newFolderCompletion(db.Counts{Bytes: 400}, db.Counts{Bytes: 50}, 0, true)) // 82.5% complete
-	if comp.CompletionPct != 90 {                                                       // 100 * (1 - 50/500)
+	comp = newFolderCompletion(db.Counts{Bytes: 100}, db.Counts{}, 0, remoteFolderValid)             // 100% complete
+	comp.add(newFolderCompletion(db.Counts{Bytes: 400}, db.Counts{Bytes: 50}, 0, remoteFolderValid)) // 82.5% complete
+	if comp.CompletionPct != 90 {                                                                    // 100 * (1 - 50/500)
 		t.Error(comp.CompletionPct)
 	}
 }

+ 5 - 1
lib/syncthing/verboseservice.go

@@ -117,7 +117,7 @@ func (s *verboseService) formatEvent(ev events.Event) string {
 
 	case events.FolderCompletion:
 		data := ev.Data.(map[string]interface{})
-		return fmt.Sprintf("Completion for folder %q on device %v is %v%% (accepted: %v)", data["folder"], data["device"], data["completion"], data["accepted"])
+		return fmt.Sprintf("Completion for folder %q on device %v is %v%% (state: %s)", data["folder"], data["device"], data["completion"], data["remoteState"])
 
 	case events.FolderSummary:
 		data := ev.Data.(model.FolderSummaryEventData)
@@ -145,6 +145,10 @@ func (s *verboseService) formatEvent(ev events.Event) string {
 		device := data["device"]
 		return fmt.Sprintf("Device %v was resumed", device)
 
+	case events.ClusterConfigReceived:
+		data := ev.Data.(model.ClusterConfigReceivedEventData)
+		return fmt.Sprintf("Received ClusterConfig from device %v", data.Device)
+
 	case events.FolderPaused:
 		data := ev.Data.(map[string]string)
 		id := data["id"]