Skip to content

Commit c25763d

Browse files
deevusmircea-pavel-antonclaude
committed
feat: add FastList option to CloudSyncTask
Add fast_list support for Cloud Sync Tasks, enabling rclone's --fast-list flag (fewer transactions in exchange for more memory). The fast_list option is stored inside the attributes JSON object in the TrueNAS API. Closes #8 Co-Authored-By: Mircea-Pavel ANTON <contact@mirceanton.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e598690 commit c25763d

File tree

2 files changed

+117
-5
lines changed

2 files changed

+117
-5
lines changed

cloudsync_service.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type CloudSyncTask struct {
3737
BWLimit []BwLimit
3838
FollowSymlinks bool
3939
CreateEmptySrcDirs bool
40+
FastList bool
4041
Enabled bool
4142
Encryption bool
4243
EncryptionPassword string
@@ -59,6 +60,7 @@ type CreateCloudSyncTaskOpts struct {
5960
BWLimit []BwLimit
6061
FollowSymlinks bool
6162
CreateEmptySrcDirs bool
63+
FastList bool
6264
Enabled bool
6365
Encryption bool
6466
EncryptionPassword string
@@ -305,9 +307,12 @@ func taskOptsToParams(opts CreateCloudSyncTaskOpts) map[string]any {
305307
},
306308
}
307309

308-
if opts.Attributes != nil {
309-
params["attributes"] = opts.Attributes
310+
attrs := opts.Attributes
311+
if attrs == nil {
312+
attrs = map[string]any{}
310313
}
314+
attrs["fast_list"] = opts.FastList
315+
params["attributes"] = attrs
311316

312317
if opts.Encryption {
313318
params["encryption_password"] = opts.EncryptionPassword
@@ -362,6 +367,10 @@ func taskFromResponse(resp CloudSyncTaskResponse) CloudSyncTask {
362367
if len(resp.Attributes) > 0 {
363368
var attrs map[string]any
364369
if err := json.Unmarshal(resp.Attributes, &attrs); err == nil {
370+
if fl, ok := attrs["fast_list"].(bool); ok {
371+
task.FastList = fl
372+
delete(attrs, "fast_list")
373+
}
365374
task.Attributes = attrs
366375
}
367376
}

cloudsync_service_conversion_test.go

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func TestTaskOptsToParams_NoOptionalFields(t *testing.T) {
222222
if _, ok := params["encryption_salt"]; ok {
223223
t.Error("unexpected encryption_salt when encryption=false")
224224
}
225-
// No bwlimit, exclude, include, attributes
225+
// No bwlimit, exclude, include
226226
if _, ok := params["bwlimit"]; ok {
227227
t.Error("unexpected bwlimit when empty")
228228
}
@@ -232,8 +232,73 @@ func TestTaskOptsToParams_NoOptionalFields(t *testing.T) {
232232
if _, ok := params["include"]; ok {
233233
t.Error("unexpected include when empty")
234234
}
235-
if _, ok := params["attributes"]; ok {
236-
t.Error("unexpected attributes when nil")
235+
// Attributes should still be present with fast_list even when Attributes is nil
236+
attrs, ok := params["attributes"].(map[string]any)
237+
if !ok {
238+
t.Fatal("expected attributes map in params")
239+
}
240+
if attrs["fast_list"] != false {
241+
t.Errorf("expected fast_list=false in attributes, got %v", attrs["fast_list"])
242+
}
243+
}
244+
245+
func TestTaskOptsToParams_FastList(t *testing.T) {
246+
opts := CreateCloudSyncTaskOpts{
247+
Description: "FastList Task",
248+
Path: "/mnt/tank",
249+
CredentialID: 1,
250+
Direction: "PUSH",
251+
TransferMode: "SYNC",
252+
FastList: true,
253+
Schedule: Schedule{
254+
Minute: "*",
255+
Hour: "*",
256+
Dom: "*",
257+
Month: "*",
258+
Dow: "*",
259+
},
260+
}
261+
262+
params := taskOptsToParams(opts)
263+
264+
attrs, ok := params["attributes"].(map[string]any)
265+
if !ok {
266+
t.Fatal("expected attributes map in params")
267+
}
268+
if attrs["fast_list"] != true {
269+
t.Errorf("expected fast_list=true in attributes, got %v", attrs["fast_list"])
270+
}
271+
}
272+
273+
func TestTaskOptsToParams_FastList_MergesWithExistingAttributes(t *testing.T) {
274+
opts := CreateCloudSyncTaskOpts{
275+
Description: "Merge Task",
276+
Path: "/mnt/tank",
277+
CredentialID: 1,
278+
Direction: "PUSH",
279+
TransferMode: "SYNC",
280+
FastList: true,
281+
Attributes: map[string]any{"bucket": "my-bucket"},
282+
Schedule: Schedule{
283+
Minute: "*",
284+
Hour: "*",
285+
Dom: "*",
286+
Month: "*",
287+
Dow: "*",
288+
},
289+
}
290+
291+
params := taskOptsToParams(opts)
292+
293+
attrs, ok := params["attributes"].(map[string]any)
294+
if !ok {
295+
t.Fatal("expected attributes map in params")
296+
}
297+
if attrs["bucket"] != "my-bucket" {
298+
t.Errorf("expected bucket=my-bucket, got %v", attrs["bucket"])
299+
}
300+
if attrs["fast_list"] != true {
301+
t.Errorf("expected fast_list=true in attributes, got %v", attrs["fast_list"])
237302
}
238303
}
239304

@@ -289,6 +354,44 @@ func TestTaskFromResponse_NullAttributes(t *testing.T) {
289354
}
290355
}
291356

357+
func TestTaskFromResponse_FastList(t *testing.T) {
358+
resp := CloudSyncTaskResponse{
359+
ID: 1,
360+
Description: "Test",
361+
Credentials: CloudSyncTaskCredentialRef{ID: 1},
362+
Attributes: json.RawMessage(`{"bucket": "my-bucket", "fast_list": true}`),
363+
Schedule: ScheduleResponse{Minute: "*", Hour: "*", Dom: "*", Month: "*", Dow: "*"},
364+
}
365+
366+
task := taskFromResponse(resp)
367+
if !task.FastList {
368+
t.Error("expected FastList=true")
369+
}
370+
// fast_list should be removed from the Attributes map
371+
if _, ok := task.Attributes["fast_list"]; ok {
372+
t.Error("fast_list should be removed from Attributes map")
373+
}
374+
// Other attributes should be preserved
375+
if task.Attributes["bucket"] != "my-bucket" {
376+
t.Errorf("expected bucket=my-bucket, got %v", task.Attributes["bucket"])
377+
}
378+
}
379+
380+
func TestTaskFromResponse_FastListFalse(t *testing.T) {
381+
resp := CloudSyncTaskResponse{
382+
ID: 1,
383+
Description: "Test",
384+
Credentials: CloudSyncTaskCredentialRef{ID: 1},
385+
Attributes: json.RawMessage(`{"bucket": "b"}`),
386+
Schedule: ScheduleResponse{Minute: "*", Hour: "*", Dom: "*", Month: "*", Dow: "*"},
387+
}
388+
389+
task := taskFromResponse(resp)
390+
if task.FastList {
391+
t.Error("expected FastList=false when not in attributes")
392+
}
393+
}
394+
292395
func TestNewCloudSyncService(t *testing.T) {
293396
mock := &mockAsyncCaller{}
294397
v := Version{Major: 25, Minor: 4}

0 commit comments

Comments
 (0)