@@ -11,34 +11,42 @@ import Combine
1111
1212class Downloader : NSObject , ObservableObject {
1313 private( set) var destination : URL
14-
14+
1515 enum DownloadState {
1616 case notStarted
1717 case downloading( Double )
1818 case completed( URL )
1919 case failed( Error )
2020 }
21-
21+
2222 enum DownloadError : Error {
2323 case invalidDownloadLocation
2424 case unexpectedError
2525 }
26-
26+
2727 private( set) lazy var downloadState : CurrentValueSubject < DownloadState , Never > = CurrentValueSubject ( . notStarted)
2828 private var stateSubscriber : Cancellable ?
29-
29+
3030 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 ) {
3333 self . destination = destination
3434 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 ? ) {
4250 downloadState. value = . downloading( 0 )
4351 urlSession? . getAllTasks { tasks in
4452 // If there's an existing pending background task with the same URL, let it proceed.
@@ -70,7 +78,7 @@ class Downloader: NSObject, ObservableObject {
7078 self . urlSession? . downloadTask ( with: request) . resume ( )
7179 }
7280 }
73-
81+
7482 @discardableResult
7583 func waitUntilDone( ) throws -> URL {
7684 // 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 {
8391 }
8492 }
8593 semaphore. wait ( )
86-
94+
8795 switch downloadState. value {
8896 case . completed( let url) : return url
8997 case . failed( let error) : throw error
9098 default : throw DownloadError . unexpectedError
9199 }
92100 }
93-
101+
94102 func cancel( ) {
95103 urlSession? . invalidateAndCancel ( )
96104 }
97105}
98106
99- extension Downloader : URLSessionDelegate , URLSessionDownloadDelegate {
107+ extension Downloader : URLSessionDownloadDelegate {
100108 func urlSession( _: URLSession , downloadTask: URLSessionDownloadTask , didWriteData _: Int64 , totalBytesWritten _: Int64 , totalBytesExpectedToWrite _: Int64 ) {
101109 downloadState. value = . downloading( downloadTask. progress. fractionCompleted)
102110 }
103111
104112 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- }
109113 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)
111116 downloadState. value = . completed( destination)
112117 } catch {
113118 downloadState. value = . failed( error)
@@ -124,3 +129,12 @@ extension Downloader: URLSessionDelegate, URLSessionDownloadDelegate {
124129 }
125130 }
126131}
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