From 0725e3af382a186495a856fe111346019c71b1e0 Mon Sep 17 00:00:00 2001 From: Nathan Morrison Date: Wed, 21 Dec 2016 16:35:20 +0000 Subject: [PATCH] all: Add a global change list GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3694 --- cmd/syncthing/main.go | 4 +- cmd/syncthing/verboseservice.go | 5 +- gui/default/index.html | 12 +- .../syncthing/core/syncthingController.js | 27 +++- .../device/globalChangesModalView.html | 27 ++++ lib/db/structs.pb.go | 28 ++++ lib/db/structs.proto | 1 + lib/events/events.go | 3 + lib/model/model.go | 28 ++-- lib/protocol/bep.pb.go | 123 +++++++++++++++--- lib/protocol/bep.proto | 1 + lib/protocol/deviceid.go | 3 + lib/scanner/walk.go | 2 + 13 files changed, 228 insertions(+), 36 deletions(-) create mode 100644 gui/default/syncthing/device/globalChangesModalView.html diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 12eef8d17..57d1cb50c 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -554,8 +554,8 @@ func syncthingMain(runtimeOptions RuntimeOptions) { // Event subscription for the API; must start early to catch the early // events. The LocalChangeDetected event might overwhelm the event // receiver in some situations so we will not subscribe to it here. - apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected), 1000) - diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected), 1000) + apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected&^events.RemoteChangeDetected), 1000) + diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected|events.RemoteChangeDetected), 1000) if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/cmd/syncthing/verboseservice.go b/cmd/syncthing/verboseservice.go index 4803c6254..9d3aff5d5 100644 --- a/cmd/syncthing/verboseservice.go +++ b/cmd/syncthing/verboseservice.go @@ -94,9 +94,12 @@ func (s *verboseService) formatEvent(ev events.Event) string { case events.LocalChangeDetected: data := ev.Data.(map[string]string) - // Local change detected in folder "foo": modified file /Users/jb/whatever return fmt.Sprintf("Local change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"]) + case events.RemoteChangeDetected: + data := ev.Data.(map[string]string) + return fmt.Sprintf("Remote change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"]) + case events.RemoteIndexUpdated: data := ev.Data.(map[string]interface{}) return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"]) diff --git a/gui/default/index.html b/gui/default/index.html index 218de2e81..eeb28e752 100644 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -617,9 +617,14 @@
- + + + +
@@ -651,6 +656,7 @@ + diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 1b0037aee..e0d51f08f 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -51,6 +51,7 @@ angular.module('syncthing.core') $scope.failedPageSize = 10; $scope.scanProgress = {}; $scope.themes = []; + $scope.globalChangeEvents = {}; $scope.localStateTotal = { bytes: 0, @@ -186,6 +187,7 @@ angular.module('syncthing.core') $scope.$on(Events.LOCAL_INDEX_UPDATED, function (event, arg) { refreshFolderStats(); + refreshGlobalChanges(); }); $scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) { @@ -629,6 +631,15 @@ angular.module('syncthing.core') }).error($scope.emitHTTPError); }, 2500); + var refreshGlobalChanges = debounce(function () { + $http.get(urlbase + "/events/disk?limit=15").success(function (data) { + data = data.reverse(); + $scope.globalChangeEvents = data; + + console.log("refreshGlobalChanges", data); + }).error($scope.emitHTTPError); + }, 2500); + $scope.refresh = function () { refreshSystem(); refreshDiscoveryCache(); @@ -912,6 +923,16 @@ angular.module('syncthing.core') return ''; }; + $scope.friendlyNameFromShort = function (shortID) { + var matches = $scope.devices.filter(function (n) { + return n.deviceID.substr(0, 7) === shortID; + }); + if (matches.length !== 1) { + return shortID; + } + return matches[0].name; + }; + $scope.findDevice = function (deviceID) { var matches = $scope.devices.filter(function (n) { return n.deviceID === deviceID; @@ -1268,7 +1289,11 @@ angular.module('syncthing.core') $scope.folderEditor = form; break; } - } + }; + + $scope.globalChanges = function () { + $('#globalChanges').modal(); + }; $scope.editFolder = function (folderCfg) { $scope.currentFolder = angular.copy(folderCfg); diff --git a/gui/default/syncthing/device/globalChangesModalView.html b/gui/default/syncthing/device/globalChangesModalView.html new file mode 100644 index 000000000..00c3f738d --- /dev/null +++ b/gui/default/syncthing/device/globalChangesModalView.html @@ -0,0 +1,27 @@ + + + + + diff --git a/lib/db/structs.pb.go b/lib/db/structs.pb.go index c63f3b029..473737390 100644 --- a/lib/db/structs.pb.go +++ b/lib/db/structs.pb.go @@ -58,6 +58,7 @@ type FileInfoTruncated struct { Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions,omitempty"` ModifiedS int64 `protobuf:"varint,5,opt,name=modified_s,json=modifiedS,proto3" json:"modified_s,omitempty"` ModifiedNs int32 `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3" json:"modified_ns,omitempty"` + ModifiedBy protocol.ShortID `protobuf:"varint,12,opt,name=modified_by,json=modifiedBy,proto3,customtype=protocol.ShortID" json:"modified_by"` Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"` Invalid bool `protobuf:"varint,7,opt,name=invalid,proto3" json:"invalid,omitempty"` NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"` @@ -226,6 +227,11 @@ func (m *FileInfoTruncated) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintStructs(data, i, uint64(m.ModifiedNs)) } + if m.ModifiedBy != 0 { + data[i] = 0x60 + i++ + i = encodeVarintStructs(data, i, uint64(m.ModifiedBy)) + } if len(m.SymlinkTarget) > 0 { data[i] = 0x8a i++ @@ -324,6 +330,9 @@ func (m *FileInfoTruncated) ProtoSize() (n int) { if m.ModifiedNs != 0 { n += 1 + sovStructs(uint64(m.ModifiedNs)) } + if m.ModifiedBy != 0 { + n += 1 + sovStructs(uint64(m.ModifiedBy)) + } l = len(m.SymlinkTarget) if l > 0 { n += 2 + l + sovStructs(uint64(l)) @@ -798,6 +807,25 @@ func (m *FileInfoTruncated) Unmarshal(data []byte) error { break } } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ModifiedBy", wireType) + } + m.ModifiedBy = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowStructs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ModifiedBy |= (protocol.ShortID(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 17: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SymlinkTarget", wireType) diff --git a/lib/db/structs.proto b/lib/db/structs.proto index 3d061af09..75d50328a 100644 --- a/lib/db/structs.proto +++ b/lib/db/structs.proto @@ -28,6 +28,7 @@ message FileInfoTruncated { uint32 permissions = 4; int64 modified_s = 5; int32 modified_ns = 11; + uint64 modified_by = 12 [(gogoproto.customtype) = "protocol.ShortID", (gogoproto.nullable) = false]; bool deleted = 6; bool invalid = 7; bool no_permissions = 8; diff --git a/lib/events/events.go b/lib/events/events.go index 9f1d043ee..25c6fcbab 100644 --- a/lib/events/events.go +++ b/lib/events/events.go @@ -29,6 +29,7 @@ const ( DevicePaused DeviceResumed LocalChangeDetected + RemoteChangeDetected LocalIndexUpdated RemoteIndexUpdated ItemStarted @@ -68,6 +69,8 @@ func (t EventType) String() string { return "DeviceRejected" case LocalChangeDetected: return "LocalChangeDetected" + case RemoteChangeDetected: + return "RemoteChangeDetected" case LocalIndexUpdated: return "LocalIndexUpdated" case RemoteIndexUpdated: diff --git a/lib/model/model.go b/lib/model/model.go index 77e5d0c81..be08c6299 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1551,12 +1551,18 @@ func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) m.fmut.RLock() folderCfg := m.folderCfgs[folder] m.fmut.RUnlock() - // Fire the LocalChangeDetected event to notify listeners about local updates. - m.localChangeDetected(folderCfg, fs) + + m.diskChangeDetected(folderCfg, fs, events.LocalChangeDetected) } func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) { m.updateLocals(folder, fs) + + m.fmut.RLock() + folderCfg := m.folderCfgs[folder] + m.fmut.RUnlock() + + m.diskChangeDetected(folderCfg, fs, events.RemoteChangeDetected) } func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) { @@ -1582,7 +1588,7 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) { }) } -func (m *Model) localChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo) { +func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo, typeOfEvent events.EventType) { path := strings.Replace(folderCfg.Path(), `\\?\`, "", 1) for _, file := range files { @@ -1611,12 +1617,14 @@ func (m *Model) localChangeDetected(folderCfg config.FolderConfiguration, files // for windows paths, strip unwanted chars from the front. path := filepath.Join(path, filepath.FromSlash(file.Name)) - events.Default.Log(events.LocalChangeDetected, map[string]string{ - "folderID": folderCfg.ID, - "label": folderCfg.Label, - "action": action, - "type": objType, - "path": path, + // Two different events can be fired here based on what EventType is passed into function + events.Default.Log(typeOfEvent, map[string]string{ + "folderID": folderCfg.ID, + "label": folderCfg.Label, + "action": action, + "type": objType, + "path": path, + "modifiedBy": file.ModifiedBy.String(), }) } } @@ -1859,6 +1867,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error Size: f.Size, ModifiedS: f.ModifiedS, ModifiedNs: f.ModifiedNs, + ModifiedBy: m.id.Short(), Permissions: f.Permissions, NoPermissions: f.NoPermissions, Invalid: true, @@ -1884,6 +1893,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error Size: 0, ModifiedS: f.ModifiedS, ModifiedNs: f.ModifiedNs, + ModifiedBy: m.id.Short(), Deleted: true, Version: f.Version.Update(m.shortID), } diff --git a/lib/protocol/bep.pb.go b/lib/protocol/bep.pb.go index 494fa1696..7d6ba839b 100644 --- a/lib/protocol/bep.pb.go +++ b/lib/protocol/bep.pb.go @@ -298,6 +298,7 @@ type FileInfo struct { Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions,omitempty"` ModifiedS int64 `protobuf:"varint,5,opt,name=modified_s,json=modifiedS,proto3" json:"modified_s,omitempty"` ModifiedNs int32 `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3" json:"modified_ns,omitempty"` + ModifiedBy ShortID `protobuf:"varint,12,opt,name=modified_by,json=modifiedBy,proto3,customtype=ShortID" json:"modified_by"` Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"` Invalid bool `protobuf:"varint,7,opt,name=invalid,proto3" json:"invalid,omitempty"` NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"` @@ -381,7 +382,7 @@ type FileDownloadProgressUpdate struct { UpdateType FileDownloadProgressUpdateType `protobuf:"varint,1,opt,name=update_type,json=updateType,proto3,enum=protocol.FileDownloadProgressUpdateType" json:"update_type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Version Vector `protobuf:"bytes,3,opt,name=version" json:"version"` - BlockIndexes []int32 `protobuf:"varint,4,rep,name=block_indexes,json=blockIndexes" json:"block_indexes,omitempty"` + BlockIndexes []int32 `protobuf:"varint,4,rep,packed,name=block_indexes,json=blockIndexes" json:"block_indexes,omitempty"` } func (m *FileDownloadProgressUpdate) Reset() { *m = FileDownloadProgressUpdate{} } @@ -858,6 +859,11 @@ func (m *FileInfo) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintBep(data, i, uint64(m.ModifiedNs)) } + if m.ModifiedBy != 0 { + data[i] = 0x60 + i++ + i = encodeVarintBep(data, i, uint64(m.ModifiedBy)) + } if len(m.Blocks) > 0 { for _, msg := range m.Blocks { data[i] = 0x82 @@ -1146,11 +1152,22 @@ func (m *FileDownloadProgressUpdate) MarshalTo(data []byte) (int, error) { } i += n3 if len(m.BlockIndexes) > 0 { - for _, num := range m.BlockIndexes { - data[i] = 0x20 - i++ - i = encodeVarintBep(data, i, uint64(num)) + data5 := make([]byte, len(m.BlockIndexes)*10) + var j4 int + for _, num1 := range m.BlockIndexes { + num := uint64(num1) + for num >= 1<<7 { + data5[j4] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j4++ + } + data5[j4] = uint8(num) + j4++ } + data[i] = 0x22 + i++ + i = encodeVarintBep(data, i, uint64(j4)) + i += copy(data[i:], data5[:j4]) } return i, nil } @@ -1403,6 +1420,9 @@ func (m *FileInfo) ProtoSize() (n int) { if m.ModifiedNs != 0 { n += 1 + sovBep(uint64(m.ModifiedNs)) } + if m.ModifiedBy != 0 { + n += 1 + sovBep(uint64(m.ModifiedBy)) + } if len(m.Blocks) > 0 { for _, e := range m.Blocks { l = e.ProtoSize() @@ -1534,9 +1554,11 @@ func (m *FileDownloadProgressUpdate) ProtoSize() (n int) { l = m.Version.ProtoSize() n += 1 + l + sovBep(uint64(l)) if len(m.BlockIndexes) > 0 { + l = 0 for _, e := range m.BlockIndexes { - n += 1 + sovBep(uint64(e)) + l += sovBep(uint64(e)) } + n += 1 + sovBep(uint64(l)) + l } return n } @@ -2841,6 +2863,25 @@ func (m *FileInfo) Unmarshal(data []byte) error { break } } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ModifiedBy", wireType) + } + m.ModifiedBy = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBep + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ModifiedBy |= (ShortID(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 16: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Blocks", wireType) @@ -3782,25 +3823,67 @@ func (m *FileDownloadProgressUpdate) Unmarshal(data []byte) error { } iNdEx = postIndex case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockIndexes", wireType) - } - var v int32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBep + if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBep + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + packedLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } } - if iNdEx >= l { + if packedLen < 0 { + return ErrInvalidLengthBep + } + postIndex := iNdEx + packedLen + if postIndex > l { return io.ErrUnexpectedEOF } - b := data[iNdEx] - iNdEx++ - v |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break + for iNdEx < postIndex { + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBep + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.BlockIndexes = append(m.BlockIndexes, v) } + } else if wireType == 0 { + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBep + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.BlockIndexes = append(m.BlockIndexes, v) + } else { + return fmt.Errorf("proto: wrong wireType = %d for field BlockIndexes", wireType) } - m.BlockIndexes = append(m.BlockIndexes, v) default: iNdEx = preIndex skippy, err := skipBep(data[iNdEx:]) diff --git a/lib/protocol/bep.proto b/lib/protocol/bep.proto index 9c97e2a0b..f658f00db 100644 --- a/lib/protocol/bep.proto +++ b/lib/protocol/bep.proto @@ -100,6 +100,7 @@ message FileInfo { uint32 permissions = 4; int64 modified_s = 5; int32 modified_ns = 11; + uint64 modified_by = 12 [(gogoproto.customtype) = "ShortID", (gogoproto.nullable) = false]; bool deleted = 6; bool invalid = 7; bool no_permissions = 8; diff --git a/lib/protocol/deviceid.go b/lib/protocol/deviceid.go index cab70e396..e2b5d15d4 100644 --- a/lib/protocol/deviceid.go +++ b/lib/protocol/deviceid.go @@ -88,6 +88,9 @@ func (n *DeviceID) MarshalText() ([]byte, error) { } func (s ShortID) String() string { + if s == 0 { + return "" + } var bs [8]byte binary.BigEndian.PutUint64(bs[:], uint64(s)) return base32.StdEncoding.EncodeToString(bs[:])[:7] diff --git a/lib/scanner/walk.go b/lib/scanner/walk.go index c296c3628..7017508f7 100644 --- a/lib/scanner/walk.go +++ b/lib/scanner/walk.go @@ -326,6 +326,7 @@ func (w *walker) walkRegular(relPath string, info os.FileInfo, fchan chan protoc NoPermissions: w.IgnorePerms, ModifiedS: info.ModTime().Unix(), ModifiedNs: int32(info.ModTime().Nanosecond()), + ModifiedBy: w.ShortID, Size: info.Size(), } l.Debugln("to hash:", relPath, f) @@ -361,6 +362,7 @@ func (w *walker) walkDir(relPath string, info os.FileInfo, dchan chan protocol.F NoPermissions: w.IgnorePerms, ModifiedS: info.ModTime().Unix(), ModifiedNs: int32(info.ModTime().Nanosecond()), + ModifiedBy: w.ShortID, } l.Debugln("dir:", relPath, f)