@@ -126,61 +126,47 @@ func uncompressArchive(at sourceURL: URL, to destinationURL: URL) throws {
126126 process. waitUntilExit ( )
127127}
128128
129- /// Downloads file from portal and write the file(s) to appropriate path(s) .
129+ /// Downloads a file from a URL to a given location .
130130/// - Parameters:
131131/// - sourceURL: The portal URL to the resource.
132- /// - downloadDirectory: The directory that stores downloaded data.
133- /// - completion: A closure to handle the results.
134- func downloadFile( at sourceURL: URL , to downloadDirectory: URL , completion: @escaping ( Result < URL , Error > ) -> Void ) {
135- let downloadTaskCompleted = { ( temporaryURL: URL ? , response: URLResponse ? , error: Error ? ) in
136- if let temporaryURL = temporaryURL,
137- let response = response,
138- let suggestedFilename = response. suggestedFilename {
139- do {
140- let downloadName : String
141- let isArchive = ( suggestedFilename as NSString ) . pathExtension == " zip "
142- // If the downloaded file is an archive and contains
143- // - 1 file, use the name of that file.
144- // - multiple files, use the suggested filename (*.zip).
145- // If it is not an archive, use the server suggested filename.
146- if isArchive {
147- let count = try count ( ofFilesInArchiveAt: temporaryURL)
148- if count == 1 {
149- downloadName = try name ( ofFileInArchiveAt: temporaryURL)
150- } else {
151- downloadName = suggestedFilename
152- }
153- } else {
154- downloadName = suggestedFilename
155- }
156-
157- let downloadURL = downloadDirectory. appendingPathComponent ( downloadName, isDirectory: false )
158-
159- if FileManager . default. fileExists ( atPath: downloadURL. path) {
160- try FileManager . default. removeItem ( at: downloadURL)
161- }
162-
163- if isArchive {
164- let extractURL = downloadURL. pathExtension == " zip "
165- // Uncompresses to directory named after archive.
166- ? downloadURL. deletingPathExtension ( )
167- // Uncompresses to appropriate subdirectory.
168- : downloadURL. deletingLastPathComponent ( )
169- try uncompressArchive ( at: temporaryURL, to: extractURL)
170- } else {
171- try FileManager . default. moveItem ( at: temporaryURL, to: downloadURL)
172- }
173-
174- completion ( . success( downloadURL) )
175- } catch {
176- completion ( . failure( error) )
177- }
178- } else if let error = error {
179- completion ( . failure( error) )
132+ /// - downloadDirectory: The directory to store the downloaded data in.
133+ /// - Throws: Exceptions when downloading, naming, uncompressing, and moving the file.
134+ /// - Returns: The name of the downloaded file.
135+ func downloadFile( from sourceURL: URL , to downloadDirectory: URL ) async throws -> String ? {
136+ let ( temporaryURL, response) = try await URLSession . shared. download ( from: sourceURL)
137+
138+ guard let suggestedFilename = response. suggestedFilename else { return nil }
139+ let isArchive = NSString ( string: suggestedFilename) . pathExtension == " zip "
140+
141+ let downloadName : String = try {
142+ // If the downloaded file is an archive and contains
143+ // - 1 file, use the name of that file.
144+ // - multiple files, use the server suggested filename (*.zip).
145+ // If it is not an archive, use the server suggested filename.
146+ if isArchive,
147+ try count ( ofFilesInArchiveAt: temporaryURL) == 1 {
148+ return try name ( ofFileInArchiveAt: temporaryURL)
149+ } else {
150+ return suggestedFilename
180151 }
152+ } ( )
153+ let downloadURL = downloadDirectory. appendingPathComponent ( downloadName, isDirectory: false )
154+
155+ try ? FileManager . default. removeItem ( at: downloadURL)
156+
157+ if isArchive {
158+ let extractURL = downloadURL. pathExtension == " zip "
159+ // Uncompresses to directory named after archive.
160+ ? downloadURL. deletingPathExtension ( )
161+ // Uncompresses to appropriate subdirectory.
162+ : downloadURL. deletingLastPathComponent ( )
163+
164+ try uncompressArchive ( at: temporaryURL, to: extractURL)
165+ } else {
166+ try FileManager . default. moveItem ( at: temporaryURL, to: downloadURL)
181167 }
182- let downloadTask = URLSession . shared . downloadTask ( with : sourceURL , completionHandler : downloadTaskCompleted )
183- downloadTask . resume ( )
168+
169+ return downloadName
184170}
185171
186172// MARK: Script Entry
@@ -242,45 +228,54 @@ let previousDownloadedItems: DownloadedItems = {
242228} ( )
243229var downloadedItems = previousDownloadedItems
244230
245- // Asynchronously downloads portal items.
246- let dispatchGroup = DispatchGroup ( )
247-
248- for portalItem in portalItems {
249- // Checks to see if an item is already downloaded.
250- guard downloadedItems [ portalItem. identifier] == nil else {
251- print ( " note: Item already downloaded: \( portalItem. identifier) " )
252- continue
253- }
254-
255- let destinationURL = downloadDirectoryURL. appendingPathComponent ( portalItem. identifier, isDirectory: true )
256-
257- // Deletes the directory when the item is not in the plist.
258- try ? FileManager . default. removeItem ( at: destinationURL)
259-
260- do {
261- // Creates an enclosing directory with portal item ID as its name.
262- try FileManager . default. createDirectory ( at: destinationURL, withIntermediateDirectories: false )
263- } catch {
264- print ( " error: Error creating download directory: \( error. localizedDescription) " )
265- exit ( 1 )
266- }
267-
268- print ( " note: Downloading item \( portalItem. identifier) " )
269- fflush ( stdout)
270- dispatchGroup. enter ( )
271- downloadFile ( at: portalItem. dataURL, to: destinationURL) { result in
272- switch result {
273- case . success( let url) :
274- downloadedItems [ portalItem. identifier] = url. lastPathComponent
275- dispatchGroup. leave ( )
276- case . failure( let error) :
277- print ( " error: Error downloading item \( portalItem. identifier) : \( error. localizedDescription) " )
278- URLSession . shared. invalidateAndCancel ( )
231+ await withTaskGroup ( of: Void . self) { group in
232+ for portalItem in portalItems {
233+ let destinationURL = downloadDirectoryURL. appendingPathComponent (
234+ portalItem. identifier,
235+ isDirectory: true
236+ )
237+
238+ // Checks to see if an item needs downloading.
239+ guard downloadedItems [ portalItem. identifier] == nil ||
240+ !FileManager. default. fileExists ( atPath: destinationURL. path) else {
241+ print ( " note: Item already downloaded: \( portalItem. identifier) " )
242+ continue
243+ }
244+
245+ // Deletes the directory when the item is not in the plist.
246+ try ? FileManager . default. removeItem ( at: destinationURL)
247+
248+ do {
249+ // Creates an enclosing directory with the portal item ID as its name.
250+ try FileManager . default. createDirectory (
251+ at: destinationURL,
252+ withIntermediateDirectories: false
253+ )
254+ } catch {
255+ print ( " error: Error creating download directory: \( error. localizedDescription) " )
279256 exit ( 1 )
280257 }
258+
259+ group. addTask {
260+ do {
261+ guard let downloadName = try await downloadFile (
262+ from: portalItem. dataURL,
263+ to: destinationURL
264+ ) else { return }
265+ print ( " note: Downloaded item: \( portalItem. identifier) " )
266+ fflush ( stdout)
267+
268+ _ = await MainActor . run {
269+ downloadedItems. updateValue ( downloadName, forKey: portalItem. identifier)
270+ }
271+ } catch {
272+ print ( " error: Error downloading item \( portalItem. identifier) : \( error. localizedDescription) " )
273+ URLSession . shared. invalidateAndCancel ( )
274+ exit ( 1 )
275+ }
276+ }
281277 }
282278}
283- dispatchGroup. wait ( )
284279
285280// Updates the downloaded items property list record if needed.
286281if downloadedItems != previousDownloadedItems {
0 commit comments