@@ -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 given portal and writes it to a given path.
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,57 @@ 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: ( Identifier, Filename? ) . self) { group in
232+ for portalItem in portalItems {
233+ // Checks to see if an item is already downloaded.
234+ guard downloadedItems [ portalItem. identifier] == nil else {
235+ print ( " note: Item already downloaded: \( portalItem. identifier) " )
236+ continue
237+ }
238+
239+ let destinationURL = downloadDirectoryURL. appendingPathComponent (
240+ portalItem. identifier,
241+ isDirectory: true
242+ )
243+
244+ // Deletes the directory when the item is not in the plist.
245+ try ? FileManager . default. removeItem ( at: destinationURL)
246+
247+ do {
248+ // Creates an enclosing directory with the portal item ID as its name.
249+ try FileManager . default. createDirectory (
250+ at: destinationURL,
251+ withIntermediateDirectories: false
252+ )
253+ } catch {
254+ print ( " error: Error creating download directory: \( error. localizedDescription) " )
279255 exit ( 1 )
280256 }
257+
258+ print ( " note: Downloading item \( portalItem. identifier) " )
259+ fflush ( stdout)
260+ group. addTask {
261+ do {
262+ let downloadName = try await downloadFile (
263+ from: portalItem. dataURL,
264+ to: destinationURL
265+ )
266+ return ( portalItem. identifier, downloadName)
267+ } catch {
268+ print ( " error: Error downloading item \( portalItem. identifier) : \( error. localizedDescription) " )
269+ URLSession . shared. invalidateAndCancel ( )
270+ exit ( 1 )
271+ }
272+ }
273+ }
274+
275+ for await (identifier, filename) in group {
276+ downloadedItems [ identifier] = filename
277+
278+ print ( " note: Downloaded item: \( identifier) " )
279+ fflush ( stdout)
281280 }
282281}
283- dispatchGroup. wait ( )
284282
285283// Updates the downloaded items property list record if needed.
286284if downloadedItems != previousDownloadedItems {
0 commit comments