refactor(downloader): clarify task states and restart action

This commit is contained in:
saltbo
2026-06-05 21:51:32 -04:00
parent 9dd3ca0105
commit 8f272b8a41
21 changed files with 4268 additions and 234 deletions
+17 -16
View File
@@ -180,8 +180,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -328,8 +328,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -706,8 +706,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -1007,8 +1007,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -1194,8 +1194,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -1472,8 +1472,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -1730,8 +1730,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
@@ -2066,6 +2066,7 @@
"resume",
"cancel",
"retry",
"restart",
"delete"
]
}
@@ -2122,8 +2123,8 @@
"enum": [
"queued",
"assigned",
"running",
"billing_paused",
"downloading",
"suspended",
"pausing",
"paused",
"interrupted",
+1 -1
View File
@@ -195,7 +195,7 @@ func (c *Client) Heartbeat(ctx context.Context, heartbeat Heartbeat) error {
func (c *Client) AssignedTasks(ctx context.Context) ([]DownloadTask, error) {
return c.assignedTasks(ctx, []openapi.GetApiDownloadTasksParamsStatus{
openapi.GetApiDownloadTasksParamsStatusAssigned,
openapi.GetApiDownloadTasksParamsStatusRunning,
openapi.GetApiDownloadTasksParamsStatusDownloading,
openapi.GetApiDownloadTasksParamsStatusInterrupted,
openapi.GetApiDownloadTasksParamsStatusUploading,
})
+5 -5
View File
@@ -53,7 +53,7 @@ func TestAssignedTasksFetchesRecoverableStatuses(t *testing.T) {
t.Fatal(err)
}
sort.Strings(statuses)
expected := []string{"assigned", "interrupted", "running", "uploading"}
expected := []string{"assigned", "downloading", "interrupted", "uploading"}
if !reflect.DeepEqual(statuses, expected) {
t.Fatalf("expected recoverable statuses %v, got %v", expected, statuses)
}
@@ -79,7 +79,7 @@ func TestUpdateTaskUsesGeneratedRequestShape(t *testing.T) {
Name: "file.bin",
TargetFolder: "",
Tags: []string{},
Status: "running",
Status: "downloading",
DownloadedBytes: 1024,
StorageUploadedBytes: 0,
DownloadBps: 10,
@@ -92,7 +92,7 @@ func TestUpdateTaskUsesGeneratedRequestShape(t *testing.T) {
totalBytes := int64(2048)
etaSeconds := int64(30)
task, err := mustClient(t, server.URL, "token").UpdateTask(context.Background(), "task-1", TaskPatch{
Status: "running",
Status: "downloading",
DownloadedBytes: &downloadedBytes,
TotalBytes: &totalBytes,
Detail: &DownloadTaskDetail{
@@ -105,10 +105,10 @@ func TestUpdateTaskUsesGeneratedRequestShape(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if task.ID != "task-1" || task.Status != "running" {
if task.ID != "task-1" || task.Status != "downloading" {
t.Fatalf("unexpected task: %#v", task)
}
if body["status"] != "running" || body["downloadedBytes"] != float64(1024) || body["totalBytes"] != float64(2048) {
if body["status"] != "downloading" || body["downloadedBytes"] != float64(1024) || body["totalBytes"] != float64(2048) {
t.Fatalf("unexpected patch body: %#v", body)
}
detail, ok := body["detail"].(map[string]any)
+1 -1
View File
@@ -247,7 +247,7 @@ func (a Aria2) Download(ctx context.Context, task client.DownloadTask, progress
}
func shouldRecoverExistingAria2Task(task client.DownloadTask) bool {
return task.Status == "running" || task.Status == "uploading"
return task.Status == "downloading" || task.Status == "uploading"
}
func addAria2Task(ctx context.Context, aria *arigo.Client, task client.DownloadTask, options *arigo.Options) (arigo.GID, error) {
+128 -125
View File
@@ -165,18 +165,18 @@ func (e PatchApiAdminDownloadersId200JSONResponseBodyStatus) Valid() bool {
// Defines values for GetApiDownloadTasksParamsStatus.
const (
GetApiDownloadTasksParamsStatusAssigned GetApiDownloadTasksParamsStatus = "assigned"
GetApiDownloadTasksParamsStatusBillingPaused GetApiDownloadTasksParamsStatus = "billing_paused"
GetApiDownloadTasksParamsStatusCanceled GetApiDownloadTasksParamsStatus = "canceled"
GetApiDownloadTasksParamsStatusCanceling GetApiDownloadTasksParamsStatus = "canceling"
GetApiDownloadTasksParamsStatusCompleted GetApiDownloadTasksParamsStatus = "completed"
GetApiDownloadTasksParamsStatusFailed GetApiDownloadTasksParamsStatus = "failed"
GetApiDownloadTasksParamsStatusInterrupted GetApiDownloadTasksParamsStatus = "interrupted"
GetApiDownloadTasksParamsStatusPaused GetApiDownloadTasksParamsStatus = "paused"
GetApiDownloadTasksParamsStatusPausing GetApiDownloadTasksParamsStatus = "pausing"
GetApiDownloadTasksParamsStatusQueued GetApiDownloadTasksParamsStatus = "queued"
GetApiDownloadTasksParamsStatusRunning GetApiDownloadTasksParamsStatus = "running"
GetApiDownloadTasksParamsStatusUploading GetApiDownloadTasksParamsStatus = "uploading"
GetApiDownloadTasksParamsStatusAssigned GetApiDownloadTasksParamsStatus = "assigned"
GetApiDownloadTasksParamsStatusCanceled GetApiDownloadTasksParamsStatus = "canceled"
GetApiDownloadTasksParamsStatusCanceling GetApiDownloadTasksParamsStatus = "canceling"
GetApiDownloadTasksParamsStatusCompleted GetApiDownloadTasksParamsStatus = "completed"
GetApiDownloadTasksParamsStatusDownloading GetApiDownloadTasksParamsStatus = "downloading"
GetApiDownloadTasksParamsStatusFailed GetApiDownloadTasksParamsStatus = "failed"
GetApiDownloadTasksParamsStatusInterrupted GetApiDownloadTasksParamsStatus = "interrupted"
GetApiDownloadTasksParamsStatusPaused GetApiDownloadTasksParamsStatus = "paused"
GetApiDownloadTasksParamsStatusPausing GetApiDownloadTasksParamsStatus = "pausing"
GetApiDownloadTasksParamsStatusQueued GetApiDownloadTasksParamsStatus = "queued"
GetApiDownloadTasksParamsStatusSuspended GetApiDownloadTasksParamsStatus = "suspended"
GetApiDownloadTasksParamsStatusUploading GetApiDownloadTasksParamsStatus = "uploading"
)
// Valid indicates whether the value is a known member of the GetApiDownloadTasksParamsStatus enum.
@@ -184,14 +184,14 @@ func (e GetApiDownloadTasksParamsStatus) Valid() bool {
switch e {
case GetApiDownloadTasksParamsStatusAssigned:
return true
case GetApiDownloadTasksParamsStatusBillingPaused:
return true
case GetApiDownloadTasksParamsStatusCanceled:
return true
case GetApiDownloadTasksParamsStatusCanceling:
return true
case GetApiDownloadTasksParamsStatusCompleted:
return true
case GetApiDownloadTasksParamsStatusDownloading:
return true
case GetApiDownloadTasksParamsStatusFailed:
return true
case GetApiDownloadTasksParamsStatusInterrupted:
@@ -202,7 +202,7 @@ func (e GetApiDownloadTasksParamsStatus) Valid() bool {
return true
case GetApiDownloadTasksParamsStatusQueued:
return true
case GetApiDownloadTasksParamsStatusRunning:
case GetApiDownloadTasksParamsStatusSuspended:
return true
case GetApiDownloadTasksParamsStatusUploading:
return true
@@ -351,18 +351,18 @@ func (e GetApiDownloadTasks200JSONResponseBodyItemsSourceType) Valid() bool {
// Defines values for GetApiDownloadTasks200JSONResponseBodyItemsStatus.
const (
GetApiDownloadTasks200JSONResponseBodyItemsStatusAssigned GetApiDownloadTasks200JSONResponseBodyItemsStatus = "assigned"
GetApiDownloadTasks200JSONResponseBodyItemsStatusBillingPaused GetApiDownloadTasks200JSONResponseBodyItemsStatus = "billing_paused"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceled GetApiDownloadTasks200JSONResponseBodyItemsStatus = "canceled"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceling GetApiDownloadTasks200JSONResponseBodyItemsStatus = "canceling"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCompleted GetApiDownloadTasks200JSONResponseBodyItemsStatus = "completed"
GetApiDownloadTasks200JSONResponseBodyItemsStatusFailed GetApiDownloadTasks200JSONResponseBodyItemsStatus = "failed"
GetApiDownloadTasks200JSONResponseBodyItemsStatusInterrupted GetApiDownloadTasks200JSONResponseBodyItemsStatus = "interrupted"
GetApiDownloadTasks200JSONResponseBodyItemsStatusPaused GetApiDownloadTasks200JSONResponseBodyItemsStatus = "paused"
GetApiDownloadTasks200JSONResponseBodyItemsStatusPausing GetApiDownloadTasks200JSONResponseBodyItemsStatus = "pausing"
GetApiDownloadTasks200JSONResponseBodyItemsStatusQueued GetApiDownloadTasks200JSONResponseBodyItemsStatus = "queued"
GetApiDownloadTasks200JSONResponseBodyItemsStatusRunning GetApiDownloadTasks200JSONResponseBodyItemsStatus = "running"
GetApiDownloadTasks200JSONResponseBodyItemsStatusUploading GetApiDownloadTasks200JSONResponseBodyItemsStatus = "uploading"
GetApiDownloadTasks200JSONResponseBodyItemsStatusAssigned GetApiDownloadTasks200JSONResponseBodyItemsStatus = "assigned"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceled GetApiDownloadTasks200JSONResponseBodyItemsStatus = "canceled"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceling GetApiDownloadTasks200JSONResponseBodyItemsStatus = "canceling"
GetApiDownloadTasks200JSONResponseBodyItemsStatusCompleted GetApiDownloadTasks200JSONResponseBodyItemsStatus = "completed"
GetApiDownloadTasks200JSONResponseBodyItemsStatusDownloading GetApiDownloadTasks200JSONResponseBodyItemsStatus = "downloading"
GetApiDownloadTasks200JSONResponseBodyItemsStatusFailed GetApiDownloadTasks200JSONResponseBodyItemsStatus = "failed"
GetApiDownloadTasks200JSONResponseBodyItemsStatusInterrupted GetApiDownloadTasks200JSONResponseBodyItemsStatus = "interrupted"
GetApiDownloadTasks200JSONResponseBodyItemsStatusPaused GetApiDownloadTasks200JSONResponseBodyItemsStatus = "paused"
GetApiDownloadTasks200JSONResponseBodyItemsStatusPausing GetApiDownloadTasks200JSONResponseBodyItemsStatus = "pausing"
GetApiDownloadTasks200JSONResponseBodyItemsStatusQueued GetApiDownloadTasks200JSONResponseBodyItemsStatus = "queued"
GetApiDownloadTasks200JSONResponseBodyItemsStatusSuspended GetApiDownloadTasks200JSONResponseBodyItemsStatus = "suspended"
GetApiDownloadTasks200JSONResponseBodyItemsStatusUploading GetApiDownloadTasks200JSONResponseBodyItemsStatus = "uploading"
)
// Valid indicates whether the value is a known member of the GetApiDownloadTasks200JSONResponseBodyItemsStatus enum.
@@ -370,14 +370,14 @@ func (e GetApiDownloadTasks200JSONResponseBodyItemsStatus) Valid() bool {
switch e {
case GetApiDownloadTasks200JSONResponseBodyItemsStatusAssigned:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusBillingPaused:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceled:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusCanceling:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusCompleted:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusDownloading:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusFailed:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusInterrupted:
@@ -388,7 +388,7 @@ func (e GetApiDownloadTasks200JSONResponseBodyItemsStatus) Valid() bool {
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusQueued:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusRunning:
case GetApiDownloadTasks200JSONResponseBodyItemsStatusSuspended:
return true
case GetApiDownloadTasks200JSONResponseBodyItemsStatusUploading:
return true
@@ -492,18 +492,18 @@ func (e PostApiDownloadTasks201JSONResponseBodySourceType) Valid() bool {
// Defines values for PostApiDownloadTasks201JSONResponseBodyStatus.
const (
PostApiDownloadTasks201JSONResponseBodyStatusAssigned PostApiDownloadTasks201JSONResponseBodyStatus = "assigned"
PostApiDownloadTasks201JSONResponseBodyStatusBillingPaused PostApiDownloadTasks201JSONResponseBodyStatus = "billing_paused"
PostApiDownloadTasks201JSONResponseBodyStatusCanceled PostApiDownloadTasks201JSONResponseBodyStatus = "canceled"
PostApiDownloadTasks201JSONResponseBodyStatusCanceling PostApiDownloadTasks201JSONResponseBodyStatus = "canceling"
PostApiDownloadTasks201JSONResponseBodyStatusCompleted PostApiDownloadTasks201JSONResponseBodyStatus = "completed"
PostApiDownloadTasks201JSONResponseBodyStatusFailed PostApiDownloadTasks201JSONResponseBodyStatus = "failed"
PostApiDownloadTasks201JSONResponseBodyStatusInterrupted PostApiDownloadTasks201JSONResponseBodyStatus = "interrupted"
PostApiDownloadTasks201JSONResponseBodyStatusPaused PostApiDownloadTasks201JSONResponseBodyStatus = "paused"
PostApiDownloadTasks201JSONResponseBodyStatusPausing PostApiDownloadTasks201JSONResponseBodyStatus = "pausing"
PostApiDownloadTasks201JSONResponseBodyStatusQueued PostApiDownloadTasks201JSONResponseBodyStatus = "queued"
PostApiDownloadTasks201JSONResponseBodyStatusRunning PostApiDownloadTasks201JSONResponseBodyStatus = "running"
PostApiDownloadTasks201JSONResponseBodyStatusUploading PostApiDownloadTasks201JSONResponseBodyStatus = "uploading"
PostApiDownloadTasks201JSONResponseBodyStatusAssigned PostApiDownloadTasks201JSONResponseBodyStatus = "assigned"
PostApiDownloadTasks201JSONResponseBodyStatusCanceled PostApiDownloadTasks201JSONResponseBodyStatus = "canceled"
PostApiDownloadTasks201JSONResponseBodyStatusCanceling PostApiDownloadTasks201JSONResponseBodyStatus = "canceling"
PostApiDownloadTasks201JSONResponseBodyStatusCompleted PostApiDownloadTasks201JSONResponseBodyStatus = "completed"
PostApiDownloadTasks201JSONResponseBodyStatusDownloading PostApiDownloadTasks201JSONResponseBodyStatus = "downloading"
PostApiDownloadTasks201JSONResponseBodyStatusFailed PostApiDownloadTasks201JSONResponseBodyStatus = "failed"
PostApiDownloadTasks201JSONResponseBodyStatusInterrupted PostApiDownloadTasks201JSONResponseBodyStatus = "interrupted"
PostApiDownloadTasks201JSONResponseBodyStatusPaused PostApiDownloadTasks201JSONResponseBodyStatus = "paused"
PostApiDownloadTasks201JSONResponseBodyStatusPausing PostApiDownloadTasks201JSONResponseBodyStatus = "pausing"
PostApiDownloadTasks201JSONResponseBodyStatusQueued PostApiDownloadTasks201JSONResponseBodyStatus = "queued"
PostApiDownloadTasks201JSONResponseBodyStatusSuspended PostApiDownloadTasks201JSONResponseBodyStatus = "suspended"
PostApiDownloadTasks201JSONResponseBodyStatusUploading PostApiDownloadTasks201JSONResponseBodyStatus = "uploading"
)
// Valid indicates whether the value is a known member of the PostApiDownloadTasks201JSONResponseBodyStatus enum.
@@ -511,14 +511,14 @@ func (e PostApiDownloadTasks201JSONResponseBodyStatus) Valid() bool {
switch e {
case PostApiDownloadTasks201JSONResponseBodyStatusAssigned:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusBillingPaused:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusCanceled:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusCanceling:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusCompleted:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusDownloading:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusFailed:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusInterrupted:
@@ -529,7 +529,7 @@ func (e PostApiDownloadTasks201JSONResponseBodyStatus) Valid() bool {
return true
case PostApiDownloadTasks201JSONResponseBodyStatusQueued:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusRunning:
case PostApiDownloadTasks201JSONResponseBodyStatusSuspended:
return true
case PostApiDownloadTasks201JSONResponseBodyStatusUploading:
return true
@@ -540,18 +540,18 @@ func (e PostApiDownloadTasks201JSONResponseBodyStatus) Valid() bool {
// Defines values for GetApiDownloadTasksEventsParamsStatus.
const (
GetApiDownloadTasksEventsParamsStatusAssigned GetApiDownloadTasksEventsParamsStatus = "assigned"
GetApiDownloadTasksEventsParamsStatusBillingPaused GetApiDownloadTasksEventsParamsStatus = "billing_paused"
GetApiDownloadTasksEventsParamsStatusCanceled GetApiDownloadTasksEventsParamsStatus = "canceled"
GetApiDownloadTasksEventsParamsStatusCanceling GetApiDownloadTasksEventsParamsStatus = "canceling"
GetApiDownloadTasksEventsParamsStatusCompleted GetApiDownloadTasksEventsParamsStatus = "completed"
GetApiDownloadTasksEventsParamsStatusFailed GetApiDownloadTasksEventsParamsStatus = "failed"
GetApiDownloadTasksEventsParamsStatusInterrupted GetApiDownloadTasksEventsParamsStatus = "interrupted"
GetApiDownloadTasksEventsParamsStatusPaused GetApiDownloadTasksEventsParamsStatus = "paused"
GetApiDownloadTasksEventsParamsStatusPausing GetApiDownloadTasksEventsParamsStatus = "pausing"
GetApiDownloadTasksEventsParamsStatusQueued GetApiDownloadTasksEventsParamsStatus = "queued"
GetApiDownloadTasksEventsParamsStatusRunning GetApiDownloadTasksEventsParamsStatus = "running"
GetApiDownloadTasksEventsParamsStatusUploading GetApiDownloadTasksEventsParamsStatus = "uploading"
GetApiDownloadTasksEventsParamsStatusAssigned GetApiDownloadTasksEventsParamsStatus = "assigned"
GetApiDownloadTasksEventsParamsStatusCanceled GetApiDownloadTasksEventsParamsStatus = "canceled"
GetApiDownloadTasksEventsParamsStatusCanceling GetApiDownloadTasksEventsParamsStatus = "canceling"
GetApiDownloadTasksEventsParamsStatusCompleted GetApiDownloadTasksEventsParamsStatus = "completed"
GetApiDownloadTasksEventsParamsStatusDownloading GetApiDownloadTasksEventsParamsStatus = "downloading"
GetApiDownloadTasksEventsParamsStatusFailed GetApiDownloadTasksEventsParamsStatus = "failed"
GetApiDownloadTasksEventsParamsStatusInterrupted GetApiDownloadTasksEventsParamsStatus = "interrupted"
GetApiDownloadTasksEventsParamsStatusPaused GetApiDownloadTasksEventsParamsStatus = "paused"
GetApiDownloadTasksEventsParamsStatusPausing GetApiDownloadTasksEventsParamsStatus = "pausing"
GetApiDownloadTasksEventsParamsStatusQueued GetApiDownloadTasksEventsParamsStatus = "queued"
GetApiDownloadTasksEventsParamsStatusSuspended GetApiDownloadTasksEventsParamsStatus = "suspended"
GetApiDownloadTasksEventsParamsStatusUploading GetApiDownloadTasksEventsParamsStatus = "uploading"
)
// Valid indicates whether the value is a known member of the GetApiDownloadTasksEventsParamsStatus enum.
@@ -559,14 +559,14 @@ func (e GetApiDownloadTasksEventsParamsStatus) Valid() bool {
switch e {
case GetApiDownloadTasksEventsParamsStatusAssigned:
return true
case GetApiDownloadTasksEventsParamsStatusBillingPaused:
return true
case GetApiDownloadTasksEventsParamsStatusCanceled:
return true
case GetApiDownloadTasksEventsParamsStatusCanceling:
return true
case GetApiDownloadTasksEventsParamsStatusCompleted:
return true
case GetApiDownloadTasksEventsParamsStatusDownloading:
return true
case GetApiDownloadTasksEventsParamsStatusFailed:
return true
case GetApiDownloadTasksEventsParamsStatusInterrupted:
@@ -577,7 +577,7 @@ func (e GetApiDownloadTasksEventsParamsStatus) Valid() bool {
return true
case GetApiDownloadTasksEventsParamsStatusQueued:
return true
case GetApiDownloadTasksEventsParamsStatusRunning:
case GetApiDownloadTasksEventsParamsStatusSuspended:
return true
case GetApiDownloadTasksEventsParamsStatusUploading:
return true
@@ -726,18 +726,18 @@ func (e GetApiDownloadTasksId200JSONResponseBodySourceType) Valid() bool {
// Defines values for GetApiDownloadTasksId200JSONResponseBodyStatus.
const (
GetApiDownloadTasksId200JSONResponseBodyStatusAssigned GetApiDownloadTasksId200JSONResponseBodyStatus = "assigned"
GetApiDownloadTasksId200JSONResponseBodyStatusBillingPaused GetApiDownloadTasksId200JSONResponseBodyStatus = "billing_paused"
GetApiDownloadTasksId200JSONResponseBodyStatusCanceled GetApiDownloadTasksId200JSONResponseBodyStatus = "canceled"
GetApiDownloadTasksId200JSONResponseBodyStatusCanceling GetApiDownloadTasksId200JSONResponseBodyStatus = "canceling"
GetApiDownloadTasksId200JSONResponseBodyStatusCompleted GetApiDownloadTasksId200JSONResponseBodyStatus = "completed"
GetApiDownloadTasksId200JSONResponseBodyStatusFailed GetApiDownloadTasksId200JSONResponseBodyStatus = "failed"
GetApiDownloadTasksId200JSONResponseBodyStatusInterrupted GetApiDownloadTasksId200JSONResponseBodyStatus = "interrupted"
GetApiDownloadTasksId200JSONResponseBodyStatusPaused GetApiDownloadTasksId200JSONResponseBodyStatus = "paused"
GetApiDownloadTasksId200JSONResponseBodyStatusPausing GetApiDownloadTasksId200JSONResponseBodyStatus = "pausing"
GetApiDownloadTasksId200JSONResponseBodyStatusQueued GetApiDownloadTasksId200JSONResponseBodyStatus = "queued"
GetApiDownloadTasksId200JSONResponseBodyStatusRunning GetApiDownloadTasksId200JSONResponseBodyStatus = "running"
GetApiDownloadTasksId200JSONResponseBodyStatusUploading GetApiDownloadTasksId200JSONResponseBodyStatus = "uploading"
GetApiDownloadTasksId200JSONResponseBodyStatusAssigned GetApiDownloadTasksId200JSONResponseBodyStatus = "assigned"
GetApiDownloadTasksId200JSONResponseBodyStatusCanceled GetApiDownloadTasksId200JSONResponseBodyStatus = "canceled"
GetApiDownloadTasksId200JSONResponseBodyStatusCanceling GetApiDownloadTasksId200JSONResponseBodyStatus = "canceling"
GetApiDownloadTasksId200JSONResponseBodyStatusCompleted GetApiDownloadTasksId200JSONResponseBodyStatus = "completed"
GetApiDownloadTasksId200JSONResponseBodyStatusDownloading GetApiDownloadTasksId200JSONResponseBodyStatus = "downloading"
GetApiDownloadTasksId200JSONResponseBodyStatusFailed GetApiDownloadTasksId200JSONResponseBodyStatus = "failed"
GetApiDownloadTasksId200JSONResponseBodyStatusInterrupted GetApiDownloadTasksId200JSONResponseBodyStatus = "interrupted"
GetApiDownloadTasksId200JSONResponseBodyStatusPaused GetApiDownloadTasksId200JSONResponseBodyStatus = "paused"
GetApiDownloadTasksId200JSONResponseBodyStatusPausing GetApiDownloadTasksId200JSONResponseBodyStatus = "pausing"
GetApiDownloadTasksId200JSONResponseBodyStatusQueued GetApiDownloadTasksId200JSONResponseBodyStatus = "queued"
GetApiDownloadTasksId200JSONResponseBodyStatusSuspended GetApiDownloadTasksId200JSONResponseBodyStatus = "suspended"
GetApiDownloadTasksId200JSONResponseBodyStatusUploading GetApiDownloadTasksId200JSONResponseBodyStatus = "uploading"
)
// Valid indicates whether the value is a known member of the GetApiDownloadTasksId200JSONResponseBodyStatus enum.
@@ -745,14 +745,14 @@ func (e GetApiDownloadTasksId200JSONResponseBodyStatus) Valid() bool {
switch e {
case GetApiDownloadTasksId200JSONResponseBodyStatusAssigned:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusBillingPaused:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusCanceled:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusCanceling:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusCompleted:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusDownloading:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusFailed:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusInterrupted:
@@ -763,7 +763,7 @@ func (e GetApiDownloadTasksId200JSONResponseBodyStatus) Valid() bool {
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusQueued:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusRunning:
case GetApiDownloadTasksId200JSONResponseBodyStatusSuspended:
return true
case GetApiDownloadTasksId200JSONResponseBodyStatusUploading:
return true
@@ -825,18 +825,18 @@ func (e PatchApiDownloadTasksIdJSONBodyDetailPhase) Valid() bool {
// Defines values for PatchApiDownloadTasksIdJSONBodyStatus.
const (
PatchApiDownloadTasksIdJSONBodyStatusAssigned PatchApiDownloadTasksIdJSONBodyStatus = "assigned"
PatchApiDownloadTasksIdJSONBodyStatusBillingPaused PatchApiDownloadTasksIdJSONBodyStatus = "billing_paused"
PatchApiDownloadTasksIdJSONBodyStatusCanceled PatchApiDownloadTasksIdJSONBodyStatus = "canceled"
PatchApiDownloadTasksIdJSONBodyStatusCanceling PatchApiDownloadTasksIdJSONBodyStatus = "canceling"
PatchApiDownloadTasksIdJSONBodyStatusCompleted PatchApiDownloadTasksIdJSONBodyStatus = "completed"
PatchApiDownloadTasksIdJSONBodyStatusFailed PatchApiDownloadTasksIdJSONBodyStatus = "failed"
PatchApiDownloadTasksIdJSONBodyStatusInterrupted PatchApiDownloadTasksIdJSONBodyStatus = "interrupted"
PatchApiDownloadTasksIdJSONBodyStatusPaused PatchApiDownloadTasksIdJSONBodyStatus = "paused"
PatchApiDownloadTasksIdJSONBodyStatusPausing PatchApiDownloadTasksIdJSONBodyStatus = "pausing"
PatchApiDownloadTasksIdJSONBodyStatusQueued PatchApiDownloadTasksIdJSONBodyStatus = "queued"
PatchApiDownloadTasksIdJSONBodyStatusRunning PatchApiDownloadTasksIdJSONBodyStatus = "running"
PatchApiDownloadTasksIdJSONBodyStatusUploading PatchApiDownloadTasksIdJSONBodyStatus = "uploading"
PatchApiDownloadTasksIdJSONBodyStatusAssigned PatchApiDownloadTasksIdJSONBodyStatus = "assigned"
PatchApiDownloadTasksIdJSONBodyStatusCanceled PatchApiDownloadTasksIdJSONBodyStatus = "canceled"
PatchApiDownloadTasksIdJSONBodyStatusCanceling PatchApiDownloadTasksIdJSONBodyStatus = "canceling"
PatchApiDownloadTasksIdJSONBodyStatusCompleted PatchApiDownloadTasksIdJSONBodyStatus = "completed"
PatchApiDownloadTasksIdJSONBodyStatusDownloading PatchApiDownloadTasksIdJSONBodyStatus = "downloading"
PatchApiDownloadTasksIdJSONBodyStatusFailed PatchApiDownloadTasksIdJSONBodyStatus = "failed"
PatchApiDownloadTasksIdJSONBodyStatusInterrupted PatchApiDownloadTasksIdJSONBodyStatus = "interrupted"
PatchApiDownloadTasksIdJSONBodyStatusPaused PatchApiDownloadTasksIdJSONBodyStatus = "paused"
PatchApiDownloadTasksIdJSONBodyStatusPausing PatchApiDownloadTasksIdJSONBodyStatus = "pausing"
PatchApiDownloadTasksIdJSONBodyStatusQueued PatchApiDownloadTasksIdJSONBodyStatus = "queued"
PatchApiDownloadTasksIdJSONBodyStatusSuspended PatchApiDownloadTasksIdJSONBodyStatus = "suspended"
PatchApiDownloadTasksIdJSONBodyStatusUploading PatchApiDownloadTasksIdJSONBodyStatus = "uploading"
)
// Valid indicates whether the value is a known member of the PatchApiDownloadTasksIdJSONBodyStatus enum.
@@ -844,14 +844,14 @@ func (e PatchApiDownloadTasksIdJSONBodyStatus) Valid() bool {
switch e {
case PatchApiDownloadTasksIdJSONBodyStatusAssigned:
return true
case PatchApiDownloadTasksIdJSONBodyStatusBillingPaused:
return true
case PatchApiDownloadTasksIdJSONBodyStatusCanceled:
return true
case PatchApiDownloadTasksIdJSONBodyStatusCanceling:
return true
case PatchApiDownloadTasksIdJSONBodyStatusCompleted:
return true
case PatchApiDownloadTasksIdJSONBodyStatusDownloading:
return true
case PatchApiDownloadTasksIdJSONBodyStatusFailed:
return true
case PatchApiDownloadTasksIdJSONBodyStatusInterrupted:
@@ -862,7 +862,7 @@ func (e PatchApiDownloadTasksIdJSONBodyStatus) Valid() bool {
return true
case PatchApiDownloadTasksIdJSONBodyStatusQueued:
return true
case PatchApiDownloadTasksIdJSONBodyStatusRunning:
case PatchApiDownloadTasksIdJSONBodyStatusSuspended:
return true
case PatchApiDownloadTasksIdJSONBodyStatusUploading:
return true
@@ -945,18 +945,18 @@ func (e PatchApiDownloadTasksId200JSONResponseBodySourceType) Valid() bool {
// Defines values for PatchApiDownloadTasksId200JSONResponseBodyStatus.
const (
PatchApiDownloadTasksId200JSONResponseBodyStatusAssigned PatchApiDownloadTasksId200JSONResponseBodyStatus = "assigned"
PatchApiDownloadTasksId200JSONResponseBodyStatusBillingPaused PatchApiDownloadTasksId200JSONResponseBodyStatus = "billing_paused"
PatchApiDownloadTasksId200JSONResponseBodyStatusCanceled PatchApiDownloadTasksId200JSONResponseBodyStatus = "canceled"
PatchApiDownloadTasksId200JSONResponseBodyStatusCanceling PatchApiDownloadTasksId200JSONResponseBodyStatus = "canceling"
PatchApiDownloadTasksId200JSONResponseBodyStatusCompleted PatchApiDownloadTasksId200JSONResponseBodyStatus = "completed"
PatchApiDownloadTasksId200JSONResponseBodyStatusFailed PatchApiDownloadTasksId200JSONResponseBodyStatus = "failed"
PatchApiDownloadTasksId200JSONResponseBodyStatusInterrupted PatchApiDownloadTasksId200JSONResponseBodyStatus = "interrupted"
PatchApiDownloadTasksId200JSONResponseBodyStatusPaused PatchApiDownloadTasksId200JSONResponseBodyStatus = "paused"
PatchApiDownloadTasksId200JSONResponseBodyStatusPausing PatchApiDownloadTasksId200JSONResponseBodyStatus = "pausing"
PatchApiDownloadTasksId200JSONResponseBodyStatusQueued PatchApiDownloadTasksId200JSONResponseBodyStatus = "queued"
PatchApiDownloadTasksId200JSONResponseBodyStatusRunning PatchApiDownloadTasksId200JSONResponseBodyStatus = "running"
PatchApiDownloadTasksId200JSONResponseBodyStatusUploading PatchApiDownloadTasksId200JSONResponseBodyStatus = "uploading"
PatchApiDownloadTasksId200JSONResponseBodyStatusAssigned PatchApiDownloadTasksId200JSONResponseBodyStatus = "assigned"
PatchApiDownloadTasksId200JSONResponseBodyStatusCanceled PatchApiDownloadTasksId200JSONResponseBodyStatus = "canceled"
PatchApiDownloadTasksId200JSONResponseBodyStatusCanceling PatchApiDownloadTasksId200JSONResponseBodyStatus = "canceling"
PatchApiDownloadTasksId200JSONResponseBodyStatusCompleted PatchApiDownloadTasksId200JSONResponseBodyStatus = "completed"
PatchApiDownloadTasksId200JSONResponseBodyStatusDownloading PatchApiDownloadTasksId200JSONResponseBodyStatus = "downloading"
PatchApiDownloadTasksId200JSONResponseBodyStatusFailed PatchApiDownloadTasksId200JSONResponseBodyStatus = "failed"
PatchApiDownloadTasksId200JSONResponseBodyStatusInterrupted PatchApiDownloadTasksId200JSONResponseBodyStatus = "interrupted"
PatchApiDownloadTasksId200JSONResponseBodyStatusPaused PatchApiDownloadTasksId200JSONResponseBodyStatus = "paused"
PatchApiDownloadTasksId200JSONResponseBodyStatusPausing PatchApiDownloadTasksId200JSONResponseBodyStatus = "pausing"
PatchApiDownloadTasksId200JSONResponseBodyStatusQueued PatchApiDownloadTasksId200JSONResponseBodyStatus = "queued"
PatchApiDownloadTasksId200JSONResponseBodyStatusSuspended PatchApiDownloadTasksId200JSONResponseBodyStatus = "suspended"
PatchApiDownloadTasksId200JSONResponseBodyStatusUploading PatchApiDownloadTasksId200JSONResponseBodyStatus = "uploading"
)
// Valid indicates whether the value is a known member of the PatchApiDownloadTasksId200JSONResponseBodyStatus enum.
@@ -964,14 +964,14 @@ func (e PatchApiDownloadTasksId200JSONResponseBodyStatus) Valid() bool {
switch e {
case PatchApiDownloadTasksId200JSONResponseBodyStatusAssigned:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusBillingPaused:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusCanceled:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusCanceling:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusCompleted:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusDownloading:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusFailed:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusInterrupted:
@@ -982,7 +982,7 @@ func (e PatchApiDownloadTasksId200JSONResponseBodyStatus) Valid() bool {
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusQueued:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusRunning:
case PatchApiDownloadTasksId200JSONResponseBodyStatusSuspended:
return true
case PatchApiDownloadTasksId200JSONResponseBodyStatusUploading:
return true
@@ -993,11 +993,12 @@ func (e PatchApiDownloadTasksId200JSONResponseBodyStatus) Valid() bool {
// Defines values for PostApiDownloadTasksIdActionsJSONBodyAction.
const (
Cancel PostApiDownloadTasksIdActionsJSONBodyAction = "cancel"
Delete PostApiDownloadTasksIdActionsJSONBodyAction = "delete"
Pause PostApiDownloadTasksIdActionsJSONBodyAction = "pause"
Resume PostApiDownloadTasksIdActionsJSONBodyAction = "resume"
Retry PostApiDownloadTasksIdActionsJSONBodyAction = "retry"
Cancel PostApiDownloadTasksIdActionsJSONBodyAction = "cancel"
Delete PostApiDownloadTasksIdActionsJSONBodyAction = "delete"
Pause PostApiDownloadTasksIdActionsJSONBodyAction = "pause"
Restart PostApiDownloadTasksIdActionsJSONBodyAction = "restart"
Resume PostApiDownloadTasksIdActionsJSONBodyAction = "resume"
Retry PostApiDownloadTasksIdActionsJSONBodyAction = "retry"
)
// Valid indicates whether the value is a known member of the PostApiDownloadTasksIdActionsJSONBodyAction enum.
@@ -1009,6 +1010,8 @@ func (e PostApiDownloadTasksIdActionsJSONBodyAction) Valid() bool {
return true
case Pause:
return true
case Restart:
return true
case Resume:
return true
case Retry:
@@ -1092,18 +1095,18 @@ func (e PostApiDownloadTasksIdActions200JSONResponseBody0SourceType) Valid() boo
// Defines values for PostApiDownloadTasksIdActions200JSONResponseBody0Status.
const (
PostApiDownloadTasksIdActions200JSONResponseBody0StatusAssigned PostApiDownloadTasksIdActions200JSONResponseBody0Status = "assigned"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusBillingPaused PostApiDownloadTasksIdActions200JSONResponseBody0Status = "billing_paused"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceled PostApiDownloadTasksIdActions200JSONResponseBody0Status = "canceled"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceling PostApiDownloadTasksIdActions200JSONResponseBody0Status = "canceling"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCompleted PostApiDownloadTasksIdActions200JSONResponseBody0Status = "completed"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusFailed PostApiDownloadTasksIdActions200JSONResponseBody0Status = "failed"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusInterrupted PostApiDownloadTasksIdActions200JSONResponseBody0Status = "interrupted"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusPaused PostApiDownloadTasksIdActions200JSONResponseBody0Status = "paused"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusPausing PostApiDownloadTasksIdActions200JSONResponseBody0Status = "pausing"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusQueued PostApiDownloadTasksIdActions200JSONResponseBody0Status = "queued"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusRunning PostApiDownloadTasksIdActions200JSONResponseBody0Status = "running"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusUploading PostApiDownloadTasksIdActions200JSONResponseBody0Status = "uploading"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusAssigned PostApiDownloadTasksIdActions200JSONResponseBody0Status = "assigned"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceled PostApiDownloadTasksIdActions200JSONResponseBody0Status = "canceled"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceling PostApiDownloadTasksIdActions200JSONResponseBody0Status = "canceling"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusCompleted PostApiDownloadTasksIdActions200JSONResponseBody0Status = "completed"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusDownloading PostApiDownloadTasksIdActions200JSONResponseBody0Status = "downloading"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusFailed PostApiDownloadTasksIdActions200JSONResponseBody0Status = "failed"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusInterrupted PostApiDownloadTasksIdActions200JSONResponseBody0Status = "interrupted"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusPaused PostApiDownloadTasksIdActions200JSONResponseBody0Status = "paused"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusPausing PostApiDownloadTasksIdActions200JSONResponseBody0Status = "pausing"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusQueued PostApiDownloadTasksIdActions200JSONResponseBody0Status = "queued"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusSuspended PostApiDownloadTasksIdActions200JSONResponseBody0Status = "suspended"
PostApiDownloadTasksIdActions200JSONResponseBody0StatusUploading PostApiDownloadTasksIdActions200JSONResponseBody0Status = "uploading"
)
// Valid indicates whether the value is a known member of the PostApiDownloadTasksIdActions200JSONResponseBody0Status enum.
@@ -1111,14 +1114,14 @@ func (e PostApiDownloadTasksIdActions200JSONResponseBody0Status) Valid() bool {
switch e {
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusAssigned:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusBillingPaused:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceled:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusCanceling:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusCompleted:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusDownloading:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusFailed:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusInterrupted:
@@ -1129,7 +1132,7 @@ func (e PostApiDownloadTasksIdActions200JSONResponseBody0Status) Valid() bool {
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusQueued:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusRunning:
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusSuspended:
return true
case PostApiDownloadTasksIdActions200JSONResponseBody0StatusUploading:
return true
+3 -3
View File
@@ -205,8 +205,8 @@ func (w *Worker) process(ctx context.Context, task client.DownloadTask) {
log.Warn("task has no recoverable completed download result; restarting download", "status", task.Status)
}
if _, err := w.updateTask(ctx, task.ID, client.TaskPatch{Status: "running"}); err != nil {
log.Error("failed to mark task running", "error", err)
if _, err := w.updateTask(ctx, task.ID, client.TaskPatch{Status: "downloading"}); err != nil {
log.Error("failed to mark task downloading", "error", err)
if w.resolveControlledTaskUpdate(ctx, task.ID, err, log) {
return
}
@@ -293,7 +293,7 @@ func resumeStage(task client.DownloadTask) taskResumeStage {
if task.Status == "uploading" {
return taskResumeUpload
}
if task.Status != "assigned" && task.Status != "running" {
if task.Status != "assigned" && task.Status != "downloading" && task.Status != "interrupted" {
return taskResumeDownload
}
if task.StorageUploadedBytes > 0 {
+12 -7
View File
@@ -163,7 +163,7 @@ func TestUploadFailurePersistsDownloadCheckpoint(t *testing.T) {
w.uploadAndComplete(
context.Background(),
slog.New(slog.NewTextHandler(io.Discard, nil)),
client.DownloadTask{ID: "task-1", Status: "running", UploadToken: "upload-token"},
client.DownloadTask{ID: "task-1", Status: "downloading", UploadToken: "upload-token"},
result,
nil,
)
@@ -262,7 +262,7 @@ func TestDownloadShutdownMarksTaskInterrupted(t *testing.T) {
w := NewWithAPI(config.Config{}, api)
w.engine = eng
w.process(context.Background(), client.DownloadTask{ID: "task-1", Status: "running"})
w.process(context.Background(), client.DownloadTask{ID: "task-1", Status: "downloading"})
patch := lastPatchWithStatus(t, api.patches, "interrupted")
if patch.DownloadBps == nil || *patch.DownloadBps != 0 {
@@ -285,7 +285,7 @@ func TestUploadShutdownMarksTaskInterrupted(t *testing.T) {
w.uploadAndComplete(
ctx,
slog.New(slog.NewTextHandler(io.Discard, nil)),
client.DownloadTask{ID: "task-1", Status: "running", UploadToken: "upload-token"},
client.DownloadTask{ID: "task-1", Status: "downloading", UploadToken: "upload-token"},
engine.Result{Path: payloadPath, Name: "payload.bin", Size: int64(len("downloaded payload"))},
nil,
)
@@ -403,15 +403,20 @@ func TestResumeStage(t *testing.T) {
want: taskResumeDownload,
},
{
name: "running completed bytes",
task: client.DownloadTask{Status: "running", DownloadedBytes: 100, TotalBytes: &total},
name: "downloading completed bytes",
task: client.DownloadTask{Status: "downloading", DownloadedBytes: 100, TotalBytes: &total},
want: taskResumeUpload,
},
{
name: "running partial download",
task: client.DownloadTask{Status: "running", DownloadedBytes: 99, TotalBytes: &total},
name: "downloading partial download",
task: client.DownloadTask{Status: "downloading", DownloadedBytes: 99, TotalBytes: &total},
want: taskResumeDownload,
},
{
name: "interrupted with uploading phase",
task: client.DownloadTask{Status: "interrupted", Detail: &client.DownloadTaskDetail{Phase: "uploading"}},
want: taskResumeUpload,
},
}
for _, tc := range cases {
@@ -0,0 +1,3 @@
-- Custom SQL migration file, put your code below! --
UPDATE `download_tasks` SET `status` = 'downloading' WHERE `status` = 'running';--> statement-breakpoint
UPDATE `download_tasks` SET `status` = 'suspended' WHERE `status` = 'billing_paused';
File diff suppressed because it is too large Load Diff
+7
View File
@@ -274,6 +274,13 @@
"when": 1780597526882,
"tag": "0039_index-download-task-classification",
"breakpoints": true
},
{
"idx": 40,
"version": "6",
"when": 1780710456844,
"tag": "0040_rename-download-task-states",
"breakpoints": true
}
]
}
@@ -203,11 +203,11 @@ describe('Download tasks API integration', () => {
'Content-Type': 'application/json',
}
const runningRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
const downloadingRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({
status: 'running',
status: 'downloading',
downloadedBytes: 10 * 1024 * 1024,
totalBytes: 10 * 1024 * 1024,
downloadBps: 512_000,
@@ -226,26 +226,26 @@ describe('Download tasks API integration', () => {
},
}),
})
expect(runningRes.status).toBe(200)
const runningTask = (await runningRes.json()) as {
expect(downloadingRes.status).toBe(200)
const downloadingTask = (await downloadingRes.json()) as {
detail: { engine: string; etaSeconds: number; infoHash: string; trackers: Array<{ url: string }> }
}
expect(runningTask.detail.engine).toBe('aria2')
expect(runningTask.detail.etaSeconds).toBe(42)
expect(runningTask.detail.infoHash).toBe('abc123')
expect(runningTask.detail.trackers[0].url).toBe('udp://tracker.example/announce')
expect(downloadingTask.detail.engine).toBe('aria2')
expect(downloadingTask.detail.etaSeconds).toBe(42)
expect(downloadingTask.detail.infoHash).toBe('abc123')
expect(downloadingTask.detail.trackers[0].url).toBe('udp://tracker.example/announce')
const recoverRunningRes = await app.request('/api/download-tasks?assignedTo=me&status=running', {
const recoverDownloadingRes = await app.request('/api/download-tasks?assignedTo=me&status=downloading', {
headers: { Authorization: `Bearer ${createdDownloader.token}` },
})
expect(recoverRunningRes.status).toBe(200)
const recoverRunning = (await recoverRunningRes.json()) as {
expect(recoverDownloadingRes.status).toBe(200)
const recoverDownloading = (await recoverDownloadingRes.json()) as {
items: Array<{ id: string; uploadToken?: string; status: string }>
}
const recoverRunningTask = recoverRunning.items.find((item) => item.id === createdTask.id)
expect(recoverRunningTask?.status).toBe('running')
expect(recoverRunningTask?.uploadToken).toBeTruthy()
uploadHeaders.Authorization = `Bearer ${recoverRunningTask?.uploadToken}`
const recoverDownloadingTask = recoverDownloading.items.find((item) => item.id === createdTask.id)
expect(recoverDownloadingTask?.status).toBe('downloading')
expect(recoverDownloadingTask?.uploadToken).toBeTruthy()
uploadHeaders.Authorization = `Bearer ${recoverDownloadingTask?.uploadToken}`
const createFolderRes = await app.request('/api/objects', {
method: 'POST',
@@ -680,10 +680,10 @@ describe('Download tasks API integration', () => {
const resumedProgressRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({ status: 'running', downloadedBytes: 2048, downloadBps: 256 }),
body: JSON.stringify({ status: 'downloading', downloadedBytes: 2048, downloadBps: 256 }),
})
expect(resumedProgressRes.status).toBe(200)
await expect(resumedProgressRes.json()).resolves.toMatchObject({ status: 'running', downloadedBytes: 2048 })
await expect(resumedProgressRes.json()).resolves.toMatchObject({ status: 'downloading', downloadedBytes: 2048 })
const pauseRes = await app.request(`/api/download-tasks/${createdTask.id}/actions`, {
method: 'POST',
@@ -703,7 +703,7 @@ describe('Download tasks API integration', () => {
const pausedProgressRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({ status: 'running', downloadedBytes: 3072 }),
body: JSON.stringify({ status: 'downloading', downloadedBytes: 3072 }),
})
expect(pausedProgressRes.status).toBe(409)
await expect(pausedProgressRes.json()).resolves.toEqual({ error: 'Task is paused' })
@@ -798,9 +798,25 @@ describe('Download tasks API integration', () => {
detail: { phase: 'uploading' },
})
expect(task?.uploadToken).toBeTruthy()
const restartRes = await app.request(`/api/download-tasks/${createdTask.id}/actions`, {
method: 'POST',
headers: { ...user, 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'restart' }),
})
expect(restartRes.status).toBe(200)
await expect(restartRes.json()).resolves.toMatchObject({
status: 'assigned',
assignedDownloaderId: createdDownloader.downloader.id,
downloadedBytes: 0,
totalBytes: null,
storageUploadedBytes: 0,
detail: null,
errorMessage: null,
})
})
it('uses transitional states for running task pause and cancel actions', async () => {
it('uses transitional states for downloading task pause and cancel actions', async () => {
const { app, db } = await createTestApp({ DOWNLOAD_TOKEN_SECRET: 'test-download-token-secret' })
await insertStorage(db)
@@ -828,12 +844,12 @@ describe('Download tasks API integration', () => {
expect(createTaskRes.status).toBe(201)
const createdTask = (await createTaskRes.json()) as { id: string; status: string }
const runningRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
const downloadingRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({ status: 'running' }),
body: JSON.stringify({ status: 'downloading' }),
})
expect(runningRes.status).toBe(200)
expect(downloadingRes.status).toBe(200)
const pauseRes = await app.request(`/api/download-tasks/${createdTask.id}/actions`, {
method: 'POST',
@@ -873,7 +889,7 @@ describe('Download tasks API integration', () => {
const rerunRes = await app.request(`/api/download-tasks/${createdTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({ status: 'running' }),
body: JSON.stringify({ status: 'downloading' }),
})
expect(rerunRes.status).toBe(200)
@@ -925,7 +941,7 @@ describe('Download tasks API integration', () => {
const billingUpdateRes = await app.request(`/api/download-tasks/${billingTask.id}`, {
method: 'PATCH',
headers: downloaderHeaders,
body: JSON.stringify({ status: 'billing_paused' }),
body: JSON.stringify({ status: 'suspended' }),
})
expect(billingUpdateRes.status).toBe(200)
const billingPauseRes = await app.request(`/api/download-tasks/${billingTask.id}/actions`, {
-1
View File
@@ -272,7 +272,6 @@ async function downloadTaskResponse(c: Context<Env>, action: () => Promise<unkno
if (error instanceof DownloadError) {
if (error.code === 'not_found') return c.json({ error: 'Not found' }, 404)
if (error.code === 'forbidden') return c.json({ error: 'Forbidden' }, 403)
if (error.code === 'billing_paused') return c.json({ error: 'insufficient_credits' }, 402)
return c.json({ error: error.message }, 409)
}
throw error
+1 -1
View File
@@ -112,7 +112,7 @@ export async function resolveTaskUploadToken(
if (task.orgId !== claims.orgId || task.targetFolder !== claims.targetFolder) return null
if (task.uploadTokenHash !== hash || task.uploadTokenJti !== claims.jti) return null
if (task.uploadTokenExpiresAt && task.uploadTokenExpiresAt.getTime() <= Date.now()) return null
if (!['assigned', 'running', 'uploading'].includes(task.status)) return null
if (!['assigned', 'downloading', 'uploading'].includes(task.status)) return null
return claims
}
+62 -17
View File
@@ -18,19 +18,29 @@ import { DownloadError, type DownloaderRow, type DownloadTaskRow } from './types
const DEFAULT_REMOTE_DOWNLOAD_UNIT_BYTES = 100 * 1024 * 1024
const UPLOAD_TOKEN_TTL_SECONDS = 24 * 60 * 60
const PAUSABLE_TASK_STATUSES = ['queued', 'assigned', 'running'] as const
const PAUSABLE_TASK_STATUSES = ['queued', 'assigned', 'downloading'] as const
const CANCELABLE_TASK_STATUSES = [
'queued',
'assigned',
'running',
'billing_paused',
'downloading',
'suspended',
'paused',
'interrupted',
'uploading',
'pausing',
] as const
const TERMINAL_TASK_STATUSES = ['completed', 'failed', 'canceled'] as const
const DOWNLOADER_TOKEN_TASK_STATUSES = ['assigned', 'running', 'uploading', 'interrupted', 'billing_paused'] as const
const RESTARTABLE_TASK_STATUSES = [
'queued',
'assigned',
'paused',
'interrupted',
'suspended',
'failed',
'canceled',
'completed',
] as const
const DOWNLOADER_TOKEN_TASK_STATUSES = ['assigned', 'downloading', 'uploading', 'interrupted'] as const
export async function createDownloader(
platform: Platform,
@@ -139,8 +149,8 @@ export async function deleteDownloader(platform: Platform, id: string): Promise<
inArray(downloadTasks.status, [
'queued',
'assigned',
'running',
'billing_paused',
'downloading',
'suspended',
'pausing',
'paused',
'interrupted',
@@ -393,7 +403,7 @@ export async function updateDownloadTask(
}
} catch (error) {
if (error instanceof RemoteDownloadBillingBlockedError) {
status = 'billing_paused'
status = 'suspended'
billingStatus = 'insufficient_credits'
} else {
throw error
@@ -420,7 +430,7 @@ export async function updateDownloadTask(
errorMessage: input.errorMessage === undefined ? task.errorMessage : input.errorMessage,
resultObjectId: input.resultObjectId === undefined ? task.resultObjectId : input.resultObjectId,
detail: input.detail === undefined ? task.detail : JSON.stringify(input.detail),
startedAt: task.startedAt ?? (status === 'running' ? now : null),
startedAt: task.startedAt ?? (status === 'downloading' ? now : null),
finishedAt: nextFinishedAt,
updatedAt: now,
})
@@ -455,9 +465,9 @@ export async function performDownloadTaskAction(
if (action === 'pause') {
if (task.status === 'paused') return toDownloadTask(task)
if (!PAUSABLE_TASK_STATUSES.includes(task.status as (typeof PAUSABLE_TASK_STATUSES)[number])) {
throw new DownloadError('invalid_state', 'Only queued, assigned, or running tasks can be paused')
throw new DownloadError('invalid_state', 'Only queued, assigned, or downloading tasks can be paused')
}
const status = task.status === 'running' ? 'pausing' : 'paused'
const status = task.status === 'downloading' ? 'pausing' : 'paused'
await platform.db
.update(downloadTasks)
.set({ status, downloadBps: 0, uploadBps: 0, updatedAt: now })
@@ -466,8 +476,8 @@ export async function performDownloadTaskAction(
}
if (action === 'resume') {
if (!['paused', 'interrupted'].includes(task.status)) {
throw new DownloadError('invalid_state', 'Only paused or interrupted tasks can be resumed')
if (!['paused', 'suspended'].includes(task.status)) {
throw new DownloadError('invalid_state', 'Only paused or suspended tasks can be resumed')
}
await platform.db
.update(downloadTasks)
@@ -490,10 +500,11 @@ export async function performDownloadTaskAction(
if (action === 'cancel') {
if (task.status === 'canceled') return toDownloadTask(task)
if (!CANCELABLE_TASK_STATUSES.includes(task.status as (typeof CANCELABLE_TASK_STATUSES)[number])) {
throw new DownloadError('invalid_state', 'Only active, interrupted, or paused tasks can be canceled')
throw new DownloadError('invalid_state', 'Only active, interrupted, suspended, or paused tasks can be canceled')
}
const status =
task.assignedDownloaderId && ['assigned', 'running', 'uploading', 'pausing', 'interrupted'].includes(task.status)
task.assignedDownloaderId &&
['assigned', 'downloading', 'uploading', 'pausing', 'interrupted'].includes(task.status)
? 'canceling'
: 'canceled'
await platform.db
@@ -510,8 +521,8 @@ export async function performDownloadTaskAction(
}
if (action === 'retry') {
if (!['failed', 'canceled'].includes(task.status)) {
throw new DownloadError('invalid_state', 'Only failed or canceled tasks can be retried')
if (task.status !== 'failed') {
throw new DownloadError('invalid_state', 'Only failed tasks can be retried')
}
await platform.db
.update(downloadTasks)
@@ -535,6 +546,40 @@ export async function performDownloadTaskAction(
return getDownloadTask(platform, orgId, id)
}
if (action === 'restart') {
if (!RESTARTABLE_TASK_STATUSES.includes(task.status as (typeof RESTARTABLE_TASK_STATUSES)[number])) {
throw new DownloadError('invalid_state', 'Only inactive tasks can be restarted')
}
await platform.db
.update(downloadTasks)
.set({
status: 'queued',
assignedDownloaderId: null,
uploadTokenHash: null,
uploadTokenJti: null,
uploadTokenExpiresAt: null,
downloadedBytes: 0,
uploadedBytes: 0,
totalBytes: null,
authorizedBytes: 0,
billedBytes: 0,
billedCredits: 0,
billingStatus: 'none',
downloadBps: 0,
uploadBps: 0,
errorMessage: null,
resultObjectId: null,
detail: null,
assignedAt: null,
startedAt: null,
finishedAt: null,
updatedAt: now,
})
.where(eq(downloadTasks.id, id))
await assignQueuedTasks(platform)
return getDownloadTask(platform, orgId, id)
}
throw new DownloadError('invalid_state')
}
@@ -542,7 +587,7 @@ export async function assertTaskUploadAllowed(platform: Platform, params: { task
const rows = await platform.db.select().from(downloadTasks).where(eq(downloadTasks.id, params.taskId)).limit(1)
const task = rows[0]
if (!task || task.assignedDownloaderId !== params.downloaderId) throw new DownloadError('forbidden')
if (!['assigned', 'running', 'uploading'].includes(task.status)) throw new DownloadError('invalid_state')
if (!['assigned', 'downloading', 'uploading'].includes(task.status)) throw new DownloadError('invalid_state')
return task
}
+1 -7
View File
@@ -2,13 +2,7 @@ import type { downloaders, downloadTasks } from '../../db/schema'
export class DownloadError extends Error {
constructor(
readonly code:
| 'not_found'
| 'forbidden'
| 'no_downloader'
| 'invalid_state'
| 'billing_paused'
| 'unsupported_source',
readonly code: 'not_found' | 'forbidden' | 'no_downloader' | 'invalid_state' | 'unsupported_source',
message: string = code,
) {
super(message)
+3 -3
View File
@@ -5,8 +5,8 @@ export const downloaderEngineSchema = z.enum(['builtin', 'aria2', 'qbittorrent']
export const downloadTaskStatusSchema = z.enum([
'queued',
'assigned',
'running',
'billing_paused',
'downloading',
'suspended',
'pausing',
'paused',
'interrupted',
@@ -16,7 +16,7 @@ export const downloadTaskStatusSchema = z.enum([
'failed',
'canceled',
])
export const downloadTaskActionSchema = z.enum(['pause', 'resume', 'cancel', 'retry', 'delete'])
export const downloadTaskActionSchema = z.enum(['pause', 'resume', 'cancel', 'retry', 'restart', 'delete'])
export const downloadSourceTypeSchema = z.enum(['http', 'magnet', 'torrent_url'])
export const downloadTaskPhaseSchema = z.enum(['metadata', 'downloading', 'uploading', 'seeding', 'completed', 'error'])
+3 -3
View File
@@ -234,8 +234,8 @@ export type DownloadSourceType = 'http' | 'magnet' | 'torrent_url'
export type DownloadTaskStatus =
| 'queued'
| 'assigned'
| 'running'
| 'billing_paused'
| 'downloading'
| 'suspended'
| 'pausing'
| 'paused'
| 'interrupted'
@@ -245,7 +245,7 @@ export type DownloadTaskStatus =
| 'failed'
| 'canceled'
export type DownloadTaskAction = 'pause' | 'resume' | 'cancel' | 'retry' | 'delete'
export type DownloadTaskAction = 'pause' | 'resume' | 'cancel' | 'retry' | 'restart' | 'delete'
export interface DownloadTask {
id: string
+3 -2
View File
@@ -157,8 +157,8 @@
"downloads.table.eta": "ETA",
"downloads.status.queued": "Queued",
"downloads.status.assigned": "Assigned",
"downloads.status.running": "Running",
"downloads.status.billing_paused": "Billing paused",
"downloads.status.downloading": "Downloading",
"downloads.status.suspended": "Suspended",
"downloads.status.pausing": "Pausing",
"downloads.status.paused": "Paused",
"downloads.status.interrupted": "Interrupted",
@@ -173,6 +173,7 @@
"downloads.actions.resume": "Resume",
"downloads.actions.cancel": "Cancel",
"downloads.actions.retry": "Retry",
"downloads.actions.restart": "Restart",
"downloads.actions.delete": "Delete",
"downloads.actions.none": "No available actions",
"downloads.confirm.cancelTitle": "Cancel download tasks?",
+3 -2
View File
@@ -157,8 +157,8 @@
"downloads.table.eta": "剩余",
"downloads.status.queued": "排队中",
"downloads.status.assigned": "已分配",
"downloads.status.running": "运行中",
"downloads.status.billing_paused": "计费暂停",
"downloads.status.downloading": "下载中",
"downloads.status.suspended": "已挂起",
"downloads.status.pausing": "暂停中",
"downloads.status.paused": "已暂停",
"downloads.status.interrupted": "已中断",
@@ -173,6 +173,7 @@
"downloads.actions.resume": "继续",
"downloads.actions.cancel": "取消",
"downloads.actions.retry": "重试",
"downloads.actions.restart": "重新下载",
"downloads.actions.delete": "删除",
"downloads.actions.none": "没有可用操作",
"downloads.confirm.cancelTitle": "取消下载任务?",
+6 -6
View File
@@ -1002,7 +1002,7 @@ describe('api', () => {
vi.mocked(fetch).mockResolvedValueOnce(makeResponse(payload))
const result = await listDownloadTasks({
status: 'running',
status: 'downloading',
assignedTo: 'me',
category: 'movies',
tag: '4k',
@@ -1015,7 +1015,7 @@ describe('api', () => {
expect(result).toEqual(payload)
const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit]
expect(url).toContain('/api/download-tasks?')
expect(url).toContain('status=running')
expect(url).toContain('status=downloading')
expect(url).toContain('assignedTo=me')
expect(url).toContain('category=movies')
expect(url).toContain('tag=4k')
@@ -1046,8 +1046,8 @@ describe('api', () => {
})
it('updates a download task', async () => {
const payload = { id: 'task-1', status: 'running' }
const body = { status: 'running' as const, downloadedBytes: 1024 }
const payload = { id: 'task-1', status: 'downloading' }
const body = { status: 'downloading' as const, downloadedBytes: 1024 }
vi.mocked(fetch).mockResolvedValueOnce(makeResponse(payload))
const result = await updateDownloadTask('task-1', body)
@@ -1080,14 +1080,14 @@ describe('api', () => {
it('builds the download task events URL from RPC client', () => {
const url = downloadTaskEventsUrl({
status: 'running',
status: 'downloading',
category: 'movies',
tag: '4k',
sortBy: 'status',
sortDir: 'desc',
})
expect(url.pathname).toBe('/api/download-tasks/events')
expect(url.searchParams.get('status')).toBe('running')
expect(url.searchParams.get('status')).toBe('downloading')
expect(url.searchParams.get('category')).toBe('movies')
expect(url.searchParams.get('tag')).toBe('4k')
expect(url.searchParams.get('sortBy')).toBe('status')
+16 -10
View File
@@ -94,14 +94,15 @@ export const Route = createFileRoute('/_authenticated/downloads/')({
const QUERY_KEY = ['download-tasks']
const EMPTY_DOWNLOAD_TASKS: DownloadTask[] = []
const PAUSABLE_STATUSES = new Set<DownloadTaskStatus>(['queued', 'assigned', 'running'])
const PAUSABLE_STATUSES = new Set<DownloadTaskStatus>(['queued', 'assigned', 'downloading'])
const SORTABLE_COLUMN_IDS = new Set(['source', 'status', 'progress', 'eta', 'category', 'tags'])
const DEFAULT_COLUMN_ORDER = ['select', 'source', 'status', 'progress', 'eta', 'category', 'tags']
const STATUS_FILTERS: Array<{ value: DownloadTaskStatus | 'all'; labelKey: string }> = [
{ value: 'all', labelKey: 'downloads.statusFilter.all' },
{ value: 'queued', labelKey: 'downloads.status.queued' },
{ value: 'running', labelKey: 'downloads.status.running' },
{ value: 'downloading', labelKey: 'downloads.status.downloading' },
{ value: 'uploading', labelKey: 'downloads.status.uploading' },
{ value: 'suspended', labelKey: 'downloads.status.suspended' },
{ value: 'paused', labelKey: 'downloads.status.paused' },
{ value: 'interrupted', labelKey: 'downloads.status.interrupted' },
{ value: 'completed', labelKey: 'downloads.status.completed' },
@@ -1270,21 +1271,25 @@ function TaskMenuItem({
function taskActions(task: DownloadTask): DownloadTaskAction[] {
if (PAUSABLE_STATUSES.has(task.status)) return ['pause', 'cancel']
if (task.status === 'paused' || task.status === 'interrupted') return ['resume', 'cancel']
if (task.status === 'billing_paused' || task.status === 'uploading' || task.status === 'pausing') return ['cancel']
if (task.status === 'failed' || task.status === 'canceled') return ['retry', 'delete']
if (task.status === 'completed') return ['delete']
if (task.status === 'paused') return ['resume', 'restart', 'cancel']
if (task.status === 'suspended') return ['resume', 'restart', 'cancel']
if (task.status === 'interrupted') return ['restart', 'cancel']
if (task.status === 'uploading' || task.status === 'pausing') return ['cancel']
if (task.status === 'failed') return ['retry', 'restart', 'delete']
if (task.status === 'canceled') return ['restart', 'delete']
if (task.status === 'completed') return ['restart', 'delete']
return []
}
function availableBulkActions(tasks: DownloadTask[]): DownloadTaskAction[] {
const orderedActions: DownloadTaskAction[] = ['pause', 'resume', 'cancel', 'retry', 'delete']
const orderedActions: DownloadTaskAction[] = ['pause', 'resume', 'cancel', 'retry', 'restart', 'delete']
return orderedActions.filter((action) => tasks.some((task) => taskActions(task).includes(action)))
}
function primaryTaskAction(task: DownloadTask): DownloadTaskAction | null {
if (PAUSABLE_STATUSES.has(task.status)) return 'pause'
if (task.status === 'paused' || task.status === 'interrupted') return 'resume'
if (task.status === 'paused' || task.status === 'suspended') return 'resume'
if (task.status === 'interrupted') return 'restart'
if (task.status === 'failed' || task.status === 'canceled') return 'retry'
return null
}
@@ -1293,6 +1298,7 @@ function TaskActionIcon({ action }: { action: DownloadTaskAction }) {
if (action === 'pause') return <PauseCircle />
if (action === 'resume') return <PlayCircle />
if (action === 'retry') return <RotateCcw />
if (action === 'restart') return <RotateCcw />
if (action === 'delete') return <Trash2 />
return <XCircle />
}
@@ -1702,11 +1708,11 @@ function StatusBadge({ status }: { status: DownloadTaskDisplayStatus }) {
'border-indigo-200 bg-indigo-50 text-indigo-700 dark:border-indigo-800 dark:bg-indigo-950/40 dark:text-indigo-300',
icon: <RadioTower />,
},
running: {
downloading: {
className: 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950/40 dark:text-blue-300',
icon: <LoaderCircle className="animate-spin" />,
},
billing_paused: {
suspended: {
className:
'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300',
icon: <PauseCircle />,