Skip to content

Commit a361c73

Browse files
committed
Add cancel single task
1 parent a3c9050 commit a361c73

File tree

9 files changed

+130
-5
lines changed

9 files changed

+130
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## Added
22
- ChunkSize argument to TUSClient initializer.
3+
- Add cancel single task.
34

45
# 3.0.0
56
- Rewrite of TUSKit

Sources/TUSKit/Scheduler.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ final class Scheduler {
3232
private var runningTasks = [ScheduledTask]()
3333
weak var delegate: SchedulerDelegate?
3434

35-
var allTasks: [ScheduledTask] { runningTasks + pendingTasks }
35+
var allTasks: [ScheduledTask] {
36+
queue.sync {
37+
runningTasks + pendingTasks
38+
}
39+
}
3640

3741
// Tasks are processed in background
3842
let queue = DispatchQueue(label: "com.TUSKit.Scheduler")
@@ -61,6 +65,28 @@ final class Scheduler {
6165
self.runningTasks = []
6266
}
6367
}
68+
69+
func cancelTasks(_ tasksToCancel: [ScheduledTask]) {
70+
queue.async {
71+
tasksToCancel.forEach { taskToCancel in
72+
if let pendingTaskIndex = self.pendingTasks.firstIndex(where: { pendingTask in
73+
pendingTask === taskToCancel
74+
}) {
75+
let pendingTask = self.pendingTasks[pendingTaskIndex]
76+
pendingTask.cancel()
77+
self.pendingTasks.remove(at: pendingTaskIndex)
78+
}
79+
80+
if let runningTaskIndex = self.runningTasks.firstIndex(where: { runningTask in
81+
runningTask === taskToCancel
82+
}) {
83+
let runningTask = self.runningTasks[runningTaskIndex]
84+
runningTask.cancel()
85+
self.runningTasks.remove(at: runningTaskIndex)
86+
}
87+
}
88+
}
89+
}
6490

6591
private func checkProcessNextTask() {
6692
queue.async { [weak self] in

Sources/TUSKit/TUSClient.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ public final class TUSClient {
121121
scheduler.cancelAll()
122122
}
123123

124+
public func cancel(id: UUID) throws {
125+
let tasksToCancel = scheduler.allTasks.filter { ($0 as? IdentifiableTask)?.id == id }
126+
scheduler.cancelTasks(tasksToCancel)
127+
}
128+
124129
/// This will cancel all running uploads and clear the local cache.
125130
/// Expect errors passed to the delegate for canceled tasks.
126131
/// - Warning: This method is destructive and will remove any stored cache.

Sources/TUSKit/Tasks/CreationTask.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import Foundation
99

1010
/// `CreationTask` Prepares the server for a file upload.
1111
/// The server will return a path to upload to.
12-
final class CreationTask: ScheduledTask {
12+
final class CreationTask: IdentifiableTask {
13+
14+
// MARK: - IdentifiableTask
15+
16+
var id: UUID {
17+
metaData.id
18+
}
1319

1420
weak var progressDelegate: ProgressDelegate?
15-
var metaData: UploadMetadata
21+
let metaData: UploadMetadata
1622

1723
private let api: TUSAPI
1824
private let files: Files
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// IdentifiableTask.swift
3+
//
4+
//
5+
// Created by Elvirion Antersijn on 26/02/2022.
6+
//
7+
8+
import Foundation
9+
10+
protocol IdentifiableTask: ScheduledTask {
11+
var id: UUID { get }
12+
}

Sources/TUSKit/Tasks/StatusTask.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
import Foundation
99

1010
/// A `StatusTask` fetches the status of an upload. It fetches the offset from we can continue uploading, and then makes a possible uploadtask.
11-
final class StatusTask: ScheduledTask {
11+
final class StatusTask: IdentifiableTask {
12+
13+
// MARK: - IdentifiableTask
14+
15+
var id: UUID {
16+
metaData.id
17+
}
1218

1319
weak var progressDelegate: ProgressDelegate?
1420
let api: TUSAPI
@@ -58,6 +64,12 @@ final class StatusTask: ScheduledTask {
5864
if offset == metaData.size {
5965
completed(.success([]))
6066
} else {
67+
// If the task has been canceled
68+
// we don't continue to create subsequent UploadDataTasks
69+
if self.didCancel {
70+
return
71+
}
72+
6173
let nextRange = offset..<min((offset + chunkSize), metaData.size)
6274

6375
let task = try UploadDataTask(api: api, metaData: metaData, files: files, range: nextRange)

Sources/TUSKit/Tasks/UploadDataTask.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import Foundation
99

1010
/// The upload task will upload to data a destination.
1111
/// Will spawn more UploadDataTasks if an upload isn't complete.
12-
final class UploadDataTask: NSObject, ScheduledTask {
12+
final class UploadDataTask: NSObject, IdentifiableTask {
13+
14+
// MARK: - IdentifiableTask
15+
16+
var id: UUID {
17+
metaData.id
18+
}
1319

1420
weak var progressDelegate: ProgressDelegate?
1521
let metaData: UploadMetadata
@@ -112,6 +118,12 @@ final class UploadDataTask: NSObject, ScheduledTask {
112118

113119
try files.encodeAndStore(metaData: metaData)
114120

121+
// If the task has been canceled
122+
// we don't continue to create subsequent UploadDataTasks
123+
if self.isCanceled {
124+
return
125+
}
126+
115127
let nextRange: Range<Int>?
116128
if let range = range {
117129
let chunkSize = range.count
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// SchedulerTests.swift
3+
//
4+
//
5+
// Created by Elvirion Antersijn on 17/03/2022.
6+
//
7+
8+
import XCTest
9+
@testable import TUSKit
10+
11+
private final class TestTask: ScheduledTask {
12+
func run(completed: @escaping TaskCompletion) {}
13+
func cancel() {}
14+
}
15+
16+
final class SchedulerTests: XCTestCase {
17+
18+
private let scheduler = Scheduler()
19+
20+
func testAddTask() {
21+
scheduler.addTask(task: TestTask())
22+
XCTAssertEqual(scheduler.allTasks.count, 1)
23+
}
24+
25+
func testCancelTask() {
26+
let taskToCancel = TestTask()
27+
scheduler.addTask(task: taskToCancel)
28+
XCTAssertEqual(scheduler.allTasks.count, 1)
29+
30+
scheduler.cancelTasks([taskToCancel])
31+
XCTAssertEqual(scheduler.allTasks.count, 0)
32+
}
33+
34+
}

Tests/TUSKitTests/TUSClient/TUSClientTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ final class TUSClientTests: XCTestCase {
5757
XCTAssertEqual(1, tusDelegate.finishedUploads.count, "Expected the previous and new upload to finish")
5858
}
5959

60+
func testCancelForID() throws {
61+
let taskIDtoCancel = try client.upload(data: data)
62+
63+
try client.cancel(id: taskIDtoCancel)
64+
65+
XCTAssert(tusDelegate.finishedUploads.isEmpty)
66+
XCTAssert(tusDelegate.failedUploads.isEmpty)
67+
XCTAssert(tusDelegate.fileErrors.isEmpty)
68+
}
69+
6070
// MARK: - Progress
6171

6272
func testProgress() throws {
@@ -111,4 +121,11 @@ final class TUSClientTests: XCTestCase {
111121
waitForExpectations(timeout: 6, handler: nil)
112122
}
113123

124+
private func waitForUploadsToStart(_ amount: Int = 1) {
125+
let uploadStartedExpectation = expectation(description: "Waiting for upload to start")
126+
uploadStartedExpectation.expectedFulfillmentCount = amount
127+
tusDelegate.startUploadExpectation = uploadStartedExpectation
128+
waitForExpectations(timeout: 6, handler: nil)
129+
}
130+
114131
}

0 commit comments

Comments
 (0)