@@ -11,34 +11,42 @@ import Combine
11
11
12
12
class Downloader : NSObject , ObservableObject {
13
13
private( set) var destination : URL
14
-
14
+
15
15
enum DownloadState {
16
16
case notStarted
17
17
case downloading( Double )
18
18
case completed( URL )
19
19
case failed( Error )
20
20
}
21
-
21
+
22
22
enum DownloadError : Error {
23
23
case invalidDownloadLocation
24
24
case unexpectedError
25
25
}
26
-
26
+
27
27
private( set) lazy var downloadState : CurrentValueSubject < DownloadState , Never > = CurrentValueSubject ( . notStarted)
28
28
private var stateSubscriber : Cancellable ?
29
-
29
+
30
30
private var urlSession : URLSession ? = nil
31
-
32
- init ( from url: URL , to destination: URL , using authToken: String ? = nil ) {
31
+
32
+ init ( from url: URL , to destination: URL , using authToken: String ? = nil , inBackground : Bool = false ) {
33
33
self . destination = destination
34
34
super. init ( )
35
-
36
- let config = URLSessionConfiguration . background ( withIdentifier: url. path)
37
- #if targetEnvironment(simulator)
38
- urlSession = URLSession ( configuration: . default, delegate: self , delegateQueue: OperationQueue ( ) )
39
- #else
40
- urlSession = URLSession ( configuration: config, delegate: self , delegateQueue: OperationQueue ( ) )
41
- #endif
35
+ let sessionIdentifier = " swift-transformers.hub.downloader "
36
+
37
+ var config = URLSessionConfiguration . default
38
+ if inBackground {
39
+ config = URLSessionConfiguration . background ( withIdentifier: sessionIdentifier)
40
+ config. isDiscretionary = false
41
+ config. sessionSendsLaunchEvents = true
42
+ }
43
+
44
+ self . urlSession = URLSession ( configuration: config, delegate: self , delegateQueue: nil )
45
+
46
+ setupDownload ( from: url, with: authToken)
47
+ }
48
+
49
+ private func setupDownload( from url: URL , with authToken: String ? ) {
42
50
downloadState. value = . downloading( 0 )
43
51
urlSession? . getAllTasks { tasks in
44
52
// If there's an existing pending background task with the same URL, let it proceed.
@@ -70,7 +78,7 @@ class Downloader: NSObject, ObservableObject {
70
78
self . urlSession? . downloadTask ( with: request) . resume ( )
71
79
}
72
80
}
73
-
81
+
74
82
@discardableResult
75
83
func waitUntilDone( ) throws -> URL {
76
84
// It's either this, or stream the bytes ourselves (add to a buffer, save to disk, etc; boring and finicky)
@@ -83,31 +91,28 @@ class Downloader: NSObject, ObservableObject {
83
91
}
84
92
}
85
93
semaphore. wait ( )
86
-
94
+
87
95
switch downloadState. value {
88
96
case . completed( let url) : return url
89
97
case . failed( let error) : throw error
90
98
default : throw DownloadError . unexpectedError
91
99
}
92
100
}
93
-
101
+
94
102
func cancel( ) {
95
103
urlSession? . invalidateAndCancel ( )
96
104
}
97
105
}
98
106
99
- extension Downloader : URLSessionDelegate , URLSessionDownloadDelegate {
107
+ extension Downloader : URLSessionDownloadDelegate {
100
108
func urlSession( _: URLSession , downloadTask: URLSessionDownloadTask , didWriteData _: Int64 , totalBytesWritten _: Int64 , totalBytesExpectedToWrite _: Int64 ) {
101
109
downloadState. value = . downloading( downloadTask. progress. fractionCompleted)
102
110
}
103
111
104
112
func urlSession( _: URLSession , downloadTask _: URLSessionDownloadTask , didFinishDownloadingTo location: URL ) {
105
- guard FileManager . default. fileExists ( atPath: location. path) else {
106
- downloadState. value = . failed( DownloadError . invalidDownloadLocation)
107
- return
108
- }
109
113
do {
110
- try FileManager . default. moveItem ( at: location, to: destination)
114
+ // If the downloaded file already exists on the filesystem, overwrite it
115
+ try FileManager . default. moveDownloadedFile ( from: location, to: self . destination)
111
116
downloadState. value = . completed( destination)
112
117
} catch {
113
118
downloadState. value = . failed( error)
@@ -124,3 +129,12 @@ extension Downloader: URLSessionDelegate, URLSessionDownloadDelegate {
124
129
}
125
130
}
126
131
}
132
+
133
+ extension FileManager {
134
+ func moveDownloadedFile( from srcURL: URL , to dstURL: URL ) throws {
135
+ if fileExists ( atPath: dstURL. path) {
136
+ try removeItem ( at: dstURL)
137
+ }
138
+ try moveItem ( at: srcURL, to: dstURL)
139
+ }
140
+ }
0 commit comments