diff --git a/lib/ur/contract/contract.go b/lib/ur/contract/contract.go index 85863ba00..ff503675b 100644 --- a/lib/ur/contract/contract.go +++ b/lib/ur/contract/contract.go @@ -13,6 +13,8 @@ import ( "reflect" "strconv" "time" + + "github.com/syncthing/syncthing/lib/util" ) type Report struct { @@ -119,6 +121,13 @@ type Report struct { PullOrder map[string]int `json:"pullOrder,omitempty" since:"3"` FilesystemType map[string]int `json:"filesystemType,omitempty" since:"3"` FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" since:"3"` + CustomMarkerName int `json:"customMarkerName,omitempty" since:"3"` + CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" since:"3"` + ModTimeWindowS []int `json:"modTimeWindowS,omitempty" since:"3"` + MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" since:"3"` + DisableFsync int `json:"disableFsync,omitempty" since:"3"` + BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" since:"3"` + CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" since:"3"` } `json:"folderUsesV3,omitempty" since:"3"` GUIStats struct { @@ -164,12 +173,7 @@ type Report struct { func New() *Report { r := &Report{} - r.FolderUsesV3.PullOrder = make(map[string]int) - r.FolderUsesV3.FilesystemType = make(map[string]int) - r.GUIStats.Theme = make(map[string]int) - r.TransportStats = make(map[string]int) - r.RescanIntvs = make([]int, 0) - r.FolderUsesV3.FsWatcherDelays = make([]int, 0) + util.FillNil(r) return r } diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go index e56130084..1ed5d7c8f 100644 --- a/lib/ur/usage_report.go +++ b/lib/ur/usage_report.go @@ -256,6 +256,19 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( report.FolderUsesV3.PullOrder[cfg.Order.String()]++ report.FolderUsesV3.FilesystemType[cfg.FilesystemType.String()]++ report.FolderUsesV3.FsWatcherDelays = append(report.FolderUsesV3.FsWatcherDelays, cfg.FSWatcherDelayS) + if cfg.MarkerName != config.DefaultMarkerName { + report.FolderUsesV3.CustomMarkerName++ + } + if cfg.CopyOwnershipFromParent { + report.FolderUsesV3.CopyOwnershipFromParent++ + } + report.FolderUsesV3.ModTimeWindowS = append(report.FolderUsesV3.ModTimeWindowS, int(cfg.ModTimeWindow().Seconds())) + report.FolderUsesV3.MaxConcurrentWrites = append(report.FolderUsesV3.MaxConcurrentWrites, cfg.MaxConcurrentWrites) + if cfg.DisableFsync { + report.FolderUsesV3.DisableFsync++ + } + report.FolderUsesV3.BlockPullOrder[cfg.BlockPullOrder.String()]++ + report.FolderUsesV3.CopyRangeMethod[cfg.CopyRangeMethod.String()]++ } sort.Ints(report.FolderUsesV3.FsWatcherDelays) diff --git a/lib/util/utils.go b/lib/util/utils.go index 2817b67c8..6b1c4da22 100644 --- a/lib/util/utils.go +++ b/lib/util/utils.go @@ -137,6 +137,38 @@ func UniqueTrimmedStrings(ss []string) []string { return us } +func FillNil(data interface{}) { + s := reflect.ValueOf(data).Elem() + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + + for f.Kind() == reflect.Ptr && f.IsZero() && f.CanSet() { + newValue := reflect.New(f.Type().Elem()) + f.Set(newValue) + f = f.Elem() + } + + if f.CanSet() { + if f.IsZero() { + switch f.Kind() { + case reflect.Map: + f.Set(reflect.MakeMap(f.Type())) + case reflect.Slice: + f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + case reflect.Chan: + f.Set(reflect.MakeChan(f.Type(), 0)) + } + } + + if f.Kind() == reflect.Struct && f.CanAddr() { + if addr := f.Addr(); addr.CanInterface() { + FillNil(addr.Interface()) + } + } + } + } +} + // FillNilSlices sets default value on slices that are still nil. func FillNilSlices(data interface{}) error { s := reflect.ValueOf(data).Elem() diff --git a/lib/util/utils_test.go b/lib/util/utils_test.go index d7e0383e9..b3eb0cc2c 100644 --- a/lib/util/utils_test.go +++ b/lib/util/utils_test.go @@ -287,3 +287,128 @@ func TestUtilStopTwicePanic(t *testing.T) { }() s.Stop() } + +func TestFillNil(t *testing.T) { + type A struct { + Slice []int + Map map[string]string + Chan chan int + } + + type B struct { + Slice *[]int + Map *map[string]string + Chan *chan int + } + + type C struct { + A A + B *B + D *****[]int + } + + c := C{} + FillNil(&c) + + if c.A.Slice == nil { + t.Error("c.A.Slice") + } + if c.A.Map == nil { + t.Error("c.A.Slice") + } + if c.A.Chan == nil { + t.Error("c.A.Chan") + } + if c.B == nil { + t.Error("c.B") + } + if c.B.Slice == nil { + t.Error("c.B.Slice") + } + if c.B.Map == nil { + t.Error("c.B.Slice") + } + if c.B.Chan == nil { + t.Error("c.B.Chan") + } + if *c.B.Slice == nil { + t.Error("*c.B.Slice") + } + if *c.B.Map == nil { + t.Error("*c.B.Slice") + } + if *c.B.Chan == nil { + t.Error("*c.B.Chan") + } + if *****c.D == nil { + t.Error("c.D") + } +} + +func TestFillNilDoesNotBulldozeSetFields(t *testing.T) { + type A struct { + Slice []int + Map map[string]string + Chan chan int + } + + type B struct { + Slice *[]int + Map *map[string]string + Chan *chan int + } + + type C struct { + A A + B *B + D **[]int + } + + ch := make(chan int, 10) + d := make([]int, 10) + dd := &d + + c := C{ + A: A{ + Slice: []int{1}, + Map: map[string]string{ + "k": "v", + }, + Chan: make(chan int, 10), + }, + B: &B{ + Slice: &[]int{1}, + Map: &map[string]string{ + "k": "v", + }, + Chan: &ch, + }, + D: &dd, + } + FillNil(&c) + + if len(c.A.Slice) != 1 { + t.Error("c.A.Slice") + } + if len(c.A.Map) != 1 { + t.Error("c.A.Slice") + } + if cap(c.A.Chan) != 10 { + t.Error("c.A.Chan") + } + if c.B == nil { + t.Error("c.B") + } + if len(*c.B.Slice) != 1 { + t.Error("c.B.Slice") + } + if len(*c.B.Map) != 1 { + t.Error("c.B.Slice") + } + if cap(*c.B.Chan) != 10 { + t.Error("c.B.Chan") + } + if cap(**c.D) != 10 { + t.Error("c.D") + } +}