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

This commit is contained in:
André Colomb 2022-04-22 08:42:20 +02:00 committed by GitHub
parent 623ec03dad
commit 1eda82b95f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 29 deletions

View File

@ -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) 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_PAUSED: 'DevicePaused', // Emitted when a device has been paused
DEVICE_RESUMED: 'DeviceResumed', // Emitted when a device has been resumed 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 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 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 FOLDER_COMPLETION: 'FolderCompletion', //Emitted when the local or remote contents for a folder changes

View File

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

View File

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

View File

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

View File

@ -55,11 +55,31 @@ func (s folderState) String() string {
type remoteFolderState int type remoteFolderState int
const ( const (
remoteValid remoteFolderState = iota remoteFolderUnknown remoteFolderState = iota
remoteNotSharing remoteFolderNotSharing
remotePaused 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 { type stateTracker struct {
folderID string folderID string
evLogger events.Logger evLogger events.Logger

View File

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

View File

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

View File

@ -117,7 +117,7 @@ func (s *verboseService) formatEvent(ev events.Event) string {
case events.FolderCompletion: case events.FolderCompletion:
data := ev.Data.(map[string]interface{}) 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: case events.FolderSummary:
data := ev.Data.(model.FolderSummaryEventData) data := ev.Data.(model.FolderSummaryEventData)
@ -145,6 +145,10 @@ func (s *verboseService) formatEvent(ev events.Event) string {
device := data["device"] device := data["device"]
return fmt.Sprintf("Device %v was resumed", 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: case events.FolderPaused:
data := ev.Data.(map[string]string) data := ev.Data.(map[string]string)
id := data["id"] id := data["id"]