@@ -29,14 +29,14 @@ class Downloader: NSObject, ObservableObject {
29
29
30
30
private( set) lazy var downloadState : CurrentValueSubject < DownloadState , Never > = CurrentValueSubject ( . notStarted)
31
31
private var stateSubscriber : Cancellable ?
32
-
32
+
33
33
private( set) var tempFilePath : URL
34
34
private( set) var expectedSize : Int ?
35
35
private( set) var downloadedSize : Int = 0
36
36
37
37
var session : URLSession ? = nil
38
38
var downloadTask : Task < Void , Error > ? = nil
39
-
39
+
40
40
init (
41
41
from url: URL ,
42
42
to destination: URL ,
@@ -50,13 +50,13 @@ class Downloader: NSObject, ObservableObject {
50
50
) {
51
51
self . destination = destination
52
52
self . expectedSize = expectedSize
53
-
53
+
54
54
// Create incomplete file path based on destination
55
55
tempFilePath = incompleteDestination
56
-
56
+
57
57
// If resume size wasn't specified, check for an existing incomplete file
58
58
let resumeSize = Self . incompleteFileSize ( at: incompleteDestination)
59
-
59
+
60
60
super. init ( )
61
61
let sessionIdentifier = " swift-transformers.hub.downloader "
62
62
@@ -81,7 +81,7 @@ class Downloader: NSObject, ObservableObject {
81
81
return fileSize
82
82
}
83
83
}
84
-
84
+
85
85
return 0
86
86
}
87
87
@@ -119,23 +119,23 @@ class Downloader: NSObject, ObservableObject {
119
119
existing. cancel ( )
120
120
}
121
121
}
122
-
122
+
123
123
self . downloadTask = Task {
124
124
do {
125
125
// Set up the request with appropriate headers
126
126
var request = URLRequest ( url: url)
127
127
var requestHeaders = headers ?? [ : ]
128
-
128
+
129
129
if let authToken {
130
130
requestHeaders [ " Authorization " ] = " Bearer \( authToken) "
131
131
}
132
-
132
+
133
133
self . downloadedSize = resumeSize
134
-
134
+
135
135
// Set Range header if we're resuming
136
136
if resumeSize > 0 {
137
137
requestHeaders [ " Range " ] = " bytes= \( resumeSize) - "
138
-
138
+
139
139
// Calculate and show initial progress
140
140
if let expectedSize, expectedSize > 0 {
141
141
let initialProgress = Double ( resumeSize) / Double( expectedSize)
@@ -146,23 +146,23 @@ class Downloader: NSObject, ObservableObject {
146
146
} else {
147
147
self . downloadState. value = . downloading( 0 )
148
148
}
149
-
149
+
150
150
request. timeoutInterval = timeout
151
151
request. allHTTPHeaderFields = requestHeaders
152
-
152
+
153
153
// Open the incomplete file for writing
154
154
let tempFile = try FileHandle ( forWritingTo: self . tempFilePath)
155
-
155
+
156
156
// If resuming, seek to end of file
157
157
if resumeSize > 0 {
158
158
try tempFile. seekToEnd ( )
159
159
}
160
-
160
+
161
161
try await self . httpGet ( request: request, tempFile: tempFile, resumeSize: self . downloadedSize, numRetries: numRetries, expectedSize: expectedSize)
162
-
162
+
163
163
// Clean up and move the completed download to its final destination
164
164
tempFile. closeFile ( )
165
-
165
+
166
166
try Task . checkCancellation ( )
167
167
try FileManager . default. moveDownloadedFile ( from: self . tempFilePath, to: self . destination)
168
168
self . downloadState. value = . completed( self . destination)
@@ -194,16 +194,16 @@ class Downloader: NSObject, ObservableObject {
194
194
guard let session else {
195
195
throw DownloadError . unexpectedError
196
196
}
197
-
197
+
198
198
// Create a new request with Range header for resuming
199
199
var newRequest = request
200
200
if resumeSize > 0 {
201
201
newRequest. setValue ( " bytes= \( resumeSize) - " , forHTTPHeaderField: " Range " )
202
202
}
203
-
203
+
204
204
// Start the download and get the byte stream
205
205
let ( asyncBytes, response) = try await session. bytes ( for: newRequest)
206
-
206
+
207
207
guard let httpResponse = response as? HTTPURLResponse else {
208
208
throw DownloadError . unexpectedError
209
209
}
@@ -213,7 +213,7 @@ class Downloader: NSObject, ObservableObject {
213
213
214
214
// Create a buffer to collect bytes before writing to disk
215
215
var buffer = Data ( capacity: chunkSize)
216
-
216
+
217
217
var newNumRetries = numRetries
218
218
do {
219
219
for try await byte in asyncBytes {
@@ -231,7 +231,7 @@ class Downloader: NSObject, ObservableObject {
231
231
}
232
232
}
233
233
}
234
-
234
+
235
235
if !buffer. isEmpty {
236
236
try tempFile. write ( contentsOf: buffer)
237
237
downloadedSize += buffer. count
@@ -243,10 +243,10 @@ class Downloader: NSObject, ObservableObject {
243
243
throw error
244
244
}
245
245
try await Task . sleep ( nanoseconds: 1_000_000_000 )
246
-
246
+
247
247
let config = URLSessionConfiguration . default
248
248
self . session = URLSession ( configuration: config, delegate: self , delegateQueue: nil )
249
-
249
+
250
250
try await httpGet (
251
251
request: request,
252
252
tempFile: tempFile,
@@ -255,14 +255,14 @@ class Downloader: NSObject, ObservableObject {
255
255
expectedSize: expectedSize
256
256
)
257
257
}
258
-
258
+
259
259
// Verify the downloaded file size matches the expected size
260
260
let actualSize = try tempFile. seekToEnd ( )
261
261
if let expectedSize, expectedSize != actualSize {
262
262
throw DownloadError . unexpectedError
263
263
}
264
264
}
265
-
265
+
266
266
@discardableResult
267
267
func waitUntilDone( ) throws -> URL {
268
268
// It's either this, or stream the bytes ourselves (add to a buffer, save to disk, etc; boring and finicky)
@@ -321,7 +321,7 @@ extension FileManager {
321
321
if fileExists ( atPath: dstURL. path ( ) ) {
322
322
try removeItem ( at: dstURL)
323
323
}
324
-
324
+
325
325
let directoryURL = dstURL. deletingLastPathComponent ( )
326
326
try createDirectory ( at: directoryURL, withIntermediateDirectories: true , attributes: nil )
327
327
0 commit comments