in Sources/Hub/HubApi.swift [384:463]
func download(progressHandler: @escaping (Double) -> Void) async throws -> URL {
let localMetadata = try hub.readDownloadMetadata(metadataPath: metadataDestination)
let remoteMetadata = try await hub.getFileMetadata(url: source)
let localCommitHash = localMetadata?.commitHash ?? ""
let remoteCommitHash = remoteMetadata.commitHash ?? ""
// Local file exists + metadata exists + commit_hash matches => return file
if hub.isValidHash(hash: remoteCommitHash, pattern: hub.commitHashPattern), downloaded, localMetadata != nil, localCommitHash == remoteCommitHash {
return destination
}
// From now on, etag, commit_hash, url and size are not empty
guard let remoteCommitHash = remoteMetadata.commitHash,
let remoteEtag = remoteMetadata.etag,
let remoteSize = remoteMetadata.size,
remoteMetadata.location != ""
else {
throw EnvironmentError.invalidMetadataError("File metadata must have been retrieved from server")
}
// Local file exists => check if it's up-to-date
if downloaded {
// etag matches => update metadata and return file
if localMetadata?.etag == remoteEtag {
try hub.writeDownloadMetadata(commitHash: remoteCommitHash, etag: remoteEtag, metadataPath: metadataDestination)
return destination
}
// etag is a sha256
// => means it's an LFS file (large)
// => let's compute local hash and compare
// => if match, update metadata and return file
if hub.isValidHash(hash: remoteEtag, pattern: hub.sha256Pattern) {
let fileHash = try hub.computeFileHash(file: destination)
if fileHash == remoteEtag {
try hub.writeDownloadMetadata(commitHash: remoteCommitHash, etag: remoteEtag, metadataPath: metadataDestination)
return destination
}
}
}
// Otherwise, let's download the file!
let incompleteDestination = repoMetadataDestination.appending(path: relativeFilename + ".\(remoteEtag).incomplete")
try prepareCacheDestination(incompleteDestination)
let downloader = Downloader(
from: source,
to: destination,
incompleteDestination: incompleteDestination,
using: hfToken,
inBackground: backgroundSession,
expectedSize: remoteSize
)
return try await withTaskCancellationHandler {
let downloadSubscriber = downloader.downloadState.sink { state in
switch state {
case let .downloading(progress):
progressHandler(progress)
case .completed, .failed, .notStarted:
break
}
}
do {
_ = try withExtendedLifetime(downloadSubscriber) {
try downloader.waitUntilDone()
}
try hub.writeDownloadMetadata(commitHash: remoteCommitHash, etag: remoteEtag, metadataPath: metadataDestination)
return destination
} catch {
// If download fails, leave the incomplete file in place for future resume
throw error
}
} onCancel: {
downloader.cancel()
}
}