From 5bbc5d5545c53afe7c65380e89faf44825cad875 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Tue, 8 Sep 2026 17:03:48 +0900 Subject: [PATCH] fs: make BwTimetable.Set replace the timetable instead of appending to it Set built the timetable with *x = append(*x, ts), so setting a bandwidth timetable on a value that already held one kept both schedules. The single-value branch of the same function has always done *x = BwTimetable{ts}, and the other multi-token Set methods in this package build into a local and assign at the end. The visible effect is through the rc API. The "main" options block registered in fs.RegisterGlobalOptions is the live globalConfig, and options/set reshapes JSON straight into it, so rclone rc options/set --json '{"main": {"BwLimit": "Mon-10:00,1Mi"}}' added to the running daemon's timetable rather than replacing it, and the older slot kept winning: LimitAt for a Sunday returned the previous 10Mi. The same applies to a _config override on a single call, since AddConfig shallow-copies the global. Building into a local also stops a failed parse from leaving the previous timetable partly overwritten, which the existing error cases already expect. --- fs/bwtimetable.go | 7 +++++-- fs/bwtimetable_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/fs/bwtimetable.go b/fs/bwtimetable.go index 3b25adb7b..bd737c8a6 100644 --- a/fs/bwtimetable.go +++ b/fs/bwtimetable.go @@ -150,6 +150,8 @@ func (x *BwTimetable) Set(s string) error { return nil } + var newTimetable BwTimetable + // Split the timetable string by both spaces and semicolons for tok := range strings.FieldsFuncSeq(s, func(r rune) bool { return r == ' ' || r == ';' @@ -178,7 +180,7 @@ func (x *BwTimetable) Set(s string) error { if err := ts.Bandwidth.Set(tv[1]); err != nil { return err } - *x = append(*x, ts) + newTimetable = append(newTimetable, ts) } } else { timespec := strings.Split(tv[0], "-") @@ -205,9 +207,10 @@ func (x *BwTimetable) Set(s string) error { if err := ts.Bandwidth.Set(tv[1]); err != nil { return err } - *x = append(*x, ts) + newTimetable = append(newTimetable, ts) } } + *x = newTimetable return nil } diff --git a/fs/bwtimetable_test.go b/fs/bwtimetable_test.go index b1d45f567..1c94c01b7 100644 --- a/fs/bwtimetable_test.go +++ b/fs/bwtimetable_test.go @@ -316,6 +316,52 @@ func TestBwTimetableSet(t *testing.T) { } } +func TestBwTimetableSetReplaces(t *testing.T) { + for _, test := range []struct { + first string + second string + want string + }{ + {"Sun-00:00,10M", "Mon-10:00,1M", "Mon-10:00,1Mi"}, + {"Mon-10:00,1M", "Mon-10:00,1M", "Mon-10:00,1Mi"}, + {"Mon-10:00,1M", "2M", "2Mi"}, + {"2M", "Mon-10:00,1M", "Mon-10:00,1Mi"}, + {"11:00,333;13:40,666", "Mon-10:00,1M", "Mon-10:00,1Mi"}, + } { + tt := BwTimetable{} + require.NoError(t, tt.Set(test.first), test.first) + require.NoError(t, tt.Set(test.second), test.second) + assert.Equal(t, test.want, tt.String(), "%q then %q", test.first, test.second) + + var want BwTimetable + require.NoError(t, want.Set(test.second)) + assert.Equal(t, want, tt, "%q then %q", test.first, test.second) + } +} + +func TestBwTimetableSetErrorKeepsPrevious(t *testing.T) { + for _, in := range []string{ + "Mon-11:00,333 bad", + "Mon-11:00,333 Tue-13:40,bad", + "Mon-11:00,333 24:01,666", + "11:00,333;bad", + } { + tt := BwTimetable{} + require.NoError(t, tt.Set("Sun-20:00,10M")) + require.Error(t, tt.Set(in), in) + assert.Equal(t, "Sun-20:00,10Mi", tt.String(), in) + } +} + +func TestBwTimetableUnmarshalJSONReplaces(t *testing.T) { + var tt BwTimetable + require.NoError(t, json.Unmarshal([]byte(`"Sun-00:00,10M"`), &tt)) + require.NoError(t, json.Unmarshal([]byte(`"Mon-10:00,1M"`), &tt)) + assert.Equal(t, BwTimetable{ + BwTimeSlot{DayOfTheWeek: 1, HHMM: 1000, Bandwidth: BwPair{Tx: 1024 * 1024, Rx: 1024 * 1024}}, + }, tt) +} + func TestBwTimetableLimitAt(t *testing.T) { for _, test := range []struct { tt BwTimetable