@@ -126,50 +126,61 @@ func uncompressArchive(at sourceURL: URL, to destinationURL: URL) throws {
126126 process. waitUntilExit ( )
127127}
128128
129- /// Downloads a file from a given portal and writes it to a given path.
129+ /// Downloads file from portal and write the file(s) to appropriate path(s) .
130130/// - Parameters:
131131/// - sourceURL: The portal URL to the resource.
132- /// - downloadDirectory: The directory to store the downloaded data in.
133- /// - Throws: Exceptions when downloading 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 portalURLRequest = URLRequest ( url: sourceURL)
137- let ( temporaryURL, response) = try await URLSession . shared. download ( for: portalURLRequest)
138-
139- guard let suggestedFilename = response. suggestedFilename else { return " " }
140- let isArchive = NSString ( string: suggestedFilename) . pathExtension == " zip "
141-
142- let downloadName : String = try {
143- // If the downloaded file is an archive and contains
144- // - 1 file, use the name of that file.
145- // - multiple files, use the suggested filename (*.zip).
146- // If it is not an archive, use the server suggested filename.
147- if isArchive,
148- try count ( ofFilesInArchiveAt: temporaryURL) == 1 {
149- return try name ( ofFileInArchiveAt: temporaryURL)
150- } else {
151- return suggestedFilename
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) )
152180 }
153- } ( )
154- let downloadURL = downloadDirectory. appendingPathComponent ( downloadName, isDirectory: false )
155-
156- if FileManager . default. fileExists ( atPath: downloadURL. path) {
157- try FileManager . default. removeItem ( at: downloadURL)
158- }
159-
160- if isArchive {
161- let extractURL = downloadURL. pathExtension == " zip "
162- // Uncompresses to directory named after archive.
163- ? downloadURL. deletingPathExtension ( )
164- // Uncompresses to appropriate subdirectory.
165- : downloadURL. deletingLastPathComponent ( )
166-
167- try uncompressArchive ( at: temporaryURL, to: extractURL)
168- } else {
169- try FileManager . default. moveItem ( at: temporaryURL, to: downloadURL)
170181 }
171-
172- return downloadName
182+ let downloadTask = URLSession . shared . downloadTask ( with : sourceURL , completionHandler : downloadTaskCompleted )
183+ downloadTask . resume ( )
173184}
174185
175186// MARK: Script Entry
@@ -231,56 +242,54 @@ let previousDownloadedItems: DownloadedItems = {
231242} ( )
232243var downloadedItems = previousDownloadedItems
233244
234- await withTaskGroup ( of: Void . self) { group in
235- for portalItem in portalItems {
236- // Checks to see if an item is already downloaded.
237- guard !downloadedItems. keys. contains ( portalItem. identifier) else {
238- print ( " note: Item already downloaded: \( portalItem. identifier) " )
239- continue
240- }
241-
242- let destinationURL = downloadDirectoryURL. appendingPathComponent ( portalItem. identifier, isDirectory: true )
243-
244- // Deletes the directory if it already exists.
245- // This happens when the item is not in the plist and needs to be redownloaded.
246- if FileManager . default. fileExists ( atPath: destinationURL. path) {
247- do {
248- print ( " note: Deleting directory: \( portalItem. identifier) " )
249- try FileManager . default. removeItem ( at: destinationURL)
250- } catch {
251- print ( " error: Error deleting downloaded directory: \( error. localizedDescription) " )
252- exit ( 1 )
253- }
254- }
255-
245+ // Asynchronously downloads portal items.
246+ let dispatchGroup = DispatchGroup ( )
247+
248+ portalItems. forEach { portalItem in
249+ // Checks to see if an item is already downloaded.
250+ guard !downloadedItems. keys. contains ( portalItem. identifier) else {
251+ print ( " note: Item already downloaded: \( portalItem. identifier) " )
252+ return
253+ }
254+
255+ let destinationURL = downloadDirectoryURL. appendingPathComponent ( portalItem. identifier, isDirectory: true )
256+
257+ // Deletes the directory if it already exists.
258+ // This happens when the item is not in the plist and needs to be redownloaded.
259+ if FileManager . default. fileExists ( atPath: destinationURL. path) {
256260 do {
257- // Creates an enclosing directory with portal item ID as its name.
258- try FileManager . default. createDirectory ( at: destinationURL, withIntermediateDirectories : false )
261+ print ( " note: Deleting directory: \( portalItem . identifier ) " )
262+ try FileManager . default. removeItem ( at: destinationURL)
259263 } catch {
260- print ( " error: Error creating download directory: \( error. localizedDescription) " )
264+ print ( " error: Error deleting downloaded directory: \( error. localizedDescription) " )
261265 exit ( 1 )
262266 }
263-
264- print ( " note: Downloading item: \( portalItem. identifier) " )
265- fflush ( stdout)
266-
267- group. addTask {
268- do {
269- let downloadName = try await downloadFile ( from: portalItem. dataURL, to: destinationURL)
270- print ( " note: Downloaded item: \( portalItem. identifier) " )
271- fflush ( stdout)
272-
273- await MainActor . run {
274- downloadedItems [ portalItem. identifier] = downloadName
275- }
276- } catch {
277- print ( " error: Error downloading item \( portalItem. identifier) : \( error. localizedDescription) " )
278- URLSession . shared. invalidateAndCancel ( )
279- exit ( 1 )
280- }
267+ }
268+
269+ do {
270+ // Creates an enclosing directory with portal item ID as its name.
271+ try FileManager . default. createDirectory ( at: destinationURL, withIntermediateDirectories: false )
272+ } catch {
273+ print ( " error: Error creating download directory: \( error. localizedDescription) " )
274+ exit ( 1 )
275+ }
276+
277+ print ( " note: Downloading item \( portalItem. identifier) " )
278+ fflush ( stdout)
279+ dispatchGroup. enter ( )
280+ downloadFile ( at: portalItem. dataURL, to: destinationURL) { result in
281+ switch result {
282+ case . success( let url) :
283+ downloadedItems [ portalItem. identifier] = url. lastPathComponent
284+ dispatchGroup. leave ( )
285+ case . failure( let error) :
286+ print ( " error: Error downloading item \( portalItem. identifier) : \( error. localizedDescription) " )
287+ URLSession . shared. invalidateAndCancel ( )
288+ exit ( 1 )
281289 }
282290 }
283291}
292+ dispatchGroup. wait ( )
284293
285294// Updates the downloaded items property list record if needed.
286295if downloadedItems != previousDownloadedItems {
0 commit comments