mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-14 01:04:14 +00:00
parent
623ec03dad
commit
1eda82b95f
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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":
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"]
|
||||||
|
Loading…
Reference in New Issue
Block a user