Skip to content

Commit dea7159

Browse files
committed
Update download script.
1 parent 40dfb0c commit dea7159

File tree

1 file changed

+79
-73
lines changed

1 file changed

+79
-73
lines changed

Scripts/DowloadPortalItemData.swift

Lines changed: 79 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct SampleDependency: Decodable {
3131
}
3232

3333
/// A Portal Item and its data URL.
34-
struct PortalItem {
34+
struct PortalItem: Hashable {
3535
static let arcGISOnlinePortalURL = URL(string: "https://www.arcgis.com")!
3636

3737
/// The identifier of the item.
@@ -125,61 +125,50 @@ func uncompressArchive(at sourceURL: URL, to destinationURL: URL) throws {
125125
process.waitUntilExit()
126126
}
127127

128-
/// Downloads file from portal and write the file(s) to appropriate path(s).
128+
/// Downloads a file from a given portal and writes it to a given path.
129129
/// - Parameters:
130130
/// - sourceURL: The portal URL to the resource.
131-
/// - downloadDirectory: The directory that stores downloaded data.
132-
/// - completion: A closure to handle the results.
133-
func downloadFile(at sourceURL: URL, to downloadDirectory: URL, completion: @escaping (Result<URL, Error>) -> Void) {
134-
let downloadTaskCompleted = { (temporaryURL: URL?, response: URLResponse?, error: Error?) in
135-
if let temporaryURL = temporaryURL,
136-
let response = response,
137-
let suggestedFilename = response.suggestedFilename {
138-
do {
139-
let downloadName: String
140-
let isArchive = (suggestedFilename as NSString).pathExtension == "zip"
141-
// If the downloaded file is an archive and contains
142-
// - 1 file, use the name of that file.
143-
// - multiple files, use the suggested filename (*.zip).
144-
// If it is not an archive, use the server suggested filename.
145-
if isArchive {
146-
let count = try count(ofFilesInArchiveAt: temporaryURL)
147-
if count == 1 {
148-
downloadName = try name(ofFileInArchiveAt: temporaryURL)
149-
} else {
150-
downloadName = suggestedFilename
151-
}
152-
} else {
153-
downloadName = suggestedFilename
154-
}
155-
156-
let downloadURL = downloadDirectory.appendingPathComponent(downloadName, isDirectory: false)
157-
158-
if FileManager.default.fileExists(atPath: downloadURL.path) {
159-
try FileManager.default.removeItem(at: downloadURL)
160-
}
161-
162-
if isArchive {
163-
let extractURL = downloadURL.pathExtension == "zip"
164-
// Uncompresses to directory named after archive.
165-
? downloadURL.deletingPathExtension()
166-
// Uncompresses to appropriate subdirectory.
167-
: downloadURL.deletingLastPathComponent()
168-
try uncompressArchive(at: temporaryURL, to: extractURL)
169-
} else {
170-
try FileManager.default.moveItem(at: temporaryURL, to: downloadURL)
171-
}
172-
173-
completion(.success(downloadURL))
174-
} catch {
175-
completion(.failure(error))
176-
}
177-
} else if let error = error {
178-
completion(.failure(error))
131+
/// - downloadDirectory: The directory to store the downloaded data in.
132+
/// - Throws: Exceptions when downloading and moving the file.
133+
/// - Returns: The name of the downloaded file.
134+
func downloadFile(from sourceURL: URL, to downloadDirectory: URL) async throws -> String {
135+
let portalURLRequest = URLRequest(url: sourceURL)
136+
let (temporaryURL, response) = try await URLSession.shared.download(for: portalURLRequest)
137+
138+
guard let suggestedFilename = response.suggestedFilename else { return "" }
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 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
179151
}
152+
}()
153+
let downloadURL = downloadDirectory.appendingPathComponent(downloadName, isDirectory: false)
154+
155+
if FileManager.default.fileExists(atPath: downloadURL.path) {
156+
try FileManager.default.removeItem(at: downloadURL)
180157
}
181-
let downloadTask = URLSession.shared.downloadTask(with: sourceURL, completionHandler: downloadTaskCompleted)
182-
downloadTask.resume()
158+
159+
if isArchive {
160+
let extractURL = downloadURL.pathExtension == "zip"
161+
// Uncompresses to directory named after archive.
162+
? downloadURL.deletingPathExtension()
163+
// Uncompresses to appropriate subdirectory.
164+
: downloadURL.deletingLastPathComponent()
165+
166+
try uncompressArchive(at: temporaryURL, to: extractURL)
167+
} else {
168+
try FileManager.default.moveItem(at: temporaryURL, to: downloadURL)
169+
}
170+
171+
return downloadName
183172
}
184173

185174
// MARK: Script Entry
@@ -207,7 +196,7 @@ if !FileManager.default.fileExists(atPath: downloadDirectoryURL.path) {
207196
}
208197

209198
/// Portal Items created from iterating through all metadata's "offline\_data".
210-
let portalItems: [PortalItem] = {
199+
let portalItems: Set<PortalItem> = {
211200
do {
212201
// Finds all subdirectories under the root Samples directory.
213202
let sampleSubDirectories = try FileManager.default
@@ -218,7 +207,7 @@ let portalItems: [PortalItem] = {
218207
// Omit the decoding errors from samples that don't have dependencies.
219208
let sampleDependencies = sampleJSONs
220209
.compactMap { try? parseJSON(at: $0) }
221-
return sampleDependencies.flatMap(\.offlineData)
210+
return Set(sampleDependencies.flatMap(\.offlineData))
222211
} catch {
223212
print("error: Error decoding Samples dependencies: \(error.localizedDescription)")
224213
exit(1)
@@ -241,39 +230,56 @@ let previousDownloadedItems: DownloadedItems = {
241230
}()
242231
var downloadedItems = previousDownloadedItems
243232

244-
// Asynchronously downloads portal items.
245-
let dispatchGroup = DispatchGroup()
246-
247-
portalItems.forEach { portalItem in
248-
let destinationURL = downloadDirectoryURL.appendingPathComponent(portalItem.identifier, isDirectory: true)
249-
// Checks if a directory exists or not, to see if an item is already downloaded.
250-
if FileManager.default.fileExists(atPath: destinationURL.path) {
251-
print("info: Item \(portalItem.identifier) has already been downloaded.")
252-
} else {
233+
await withTaskGroup(of: Void.self) { group in
234+
for portalItem in portalItems {
235+
// Checks to see if an item is already downloaded.
236+
guard downloadedItems[portalItem.identifier] == nil else {
237+
print("note: Item already downloaded: \(portalItem.identifier)")
238+
continue
239+
}
240+
241+
let destinationURL = downloadDirectoryURL.appendingPathComponent(portalItem.identifier, isDirectory: true)
242+
243+
// Deletes the directory if it already exists.
244+
// This happens when the item is not in the plist and needs to be redownloaded.
245+
if FileManager.default.fileExists(atPath: destinationURL.path) {
246+
do {
247+
print("note: Deleting directory: \(portalItem.identifier)")
248+
try FileManager.default.removeItem(at: destinationURL)
249+
} catch {
250+
print("error: Error deleting downloaded directory: \(error.localizedDescription)")
251+
exit(1)
252+
}
253+
}
254+
253255
do {
254256
// Creates an enclosing directory with portal item ID as its name.
255257
try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: false)
256258
} catch {
257-
print("error: Error creating download directory: \(error.localizedDescription).")
259+
print("error: Error creating download directory: \(error.localizedDescription)")
258260
exit(1)
259261
}
260-
print("info: Downloading item \(portalItem.identifier)")
262+
263+
print("note: Downloading item: \(portalItem.identifier)")
261264
fflush(stdout)
262-
dispatchGroup.enter()
263-
downloadFile(at: portalItem.dataURL, to: destinationURL) { result in
264-
switch result {
265-
case .success(let url):
266-
downloadedItems[portalItem.identifier] = url.lastPathComponent
267-
dispatchGroup.leave()
268-
case .failure(let error):
265+
266+
group.addTask {
267+
do {
268+
let downloadName = try await downloadFile(from: portalItem.dataURL, to: destinationURL)
269+
print("note: Downloaded item: \(portalItem.identifier)")
270+
fflush(stdout)
271+
272+
await MainActor.run {
273+
downloadedItems[portalItem.identifier] = downloadName
274+
}
275+
} catch {
269276
print("error: Error downloading item \(portalItem.identifier): \(error.localizedDescription)")
270277
URLSession.shared.invalidateAndCancel()
271278
exit(1)
272279
}
273280
}
274281
}
275282
}
276-
dispatchGroup.wait()
277283

278284
// Updates the downloaded items property list record if needed.
279285
if downloadedItems != previousDownloadedItems {

0 commit comments

Comments
 (0)