Skip HEAD for proxied content downloads.

This commit is contained in:
Matthew Chen 2019-02-20 14:43:53 -05:00
parent 089eec4136
commit f006972c39

View file

@ -162,8 +162,6 @@ public class ProxiedContentAssetRequest: NSObject {
AssertIsOnMainThread()
assert(oldValue == 0)
assert(contentLength > 0)
createSegments()
}
}
public weak var contentLengthTask: URLSessionDataTask?
@ -219,7 +217,7 @@ public class ProxiedContentAssetRequest: NSObject {
return contentLength
}
private func createSegments() {
fileprivate func createSegments(withInitialData initialData: Data) {
AssertIsOnMainThread()
let segmentLength = segmentSize()
@ -228,8 +226,20 @@ public class ProxiedContentAssetRequest: NSObject {
}
let contentLength = UInt(self.contentLength)
var nextSegmentStart: UInt = 0
var index: UInt = 0
// Make the initial segment.
let assetSegment = ProxiedContentAssetSegment(index: 0,
segmentStart: 0,
segmentLength: UInt(initialData.count),
redundantLength: 0)
// "Download" the initial segment using the initialData.
assetSegment.state = .downloading
assetSegment.append(data: initialData)
// Mark the initial segment as complete.
assetSegment.state = .complete
segments.append(assetSegment)
var nextSegmentStart = UInt(initialData.count)
var index: UInt = 1
while nextSegmentStart < contentLength {
var segmentStart: UInt = nextSegmentStart
var redundantLength: UInt = 0
@ -549,31 +559,40 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
DispatchQueue.main.async {
assetSegment.state = .complete
if assetRequest.areAllSegmentsComplete() {
// If the asset request has completed all of its segments,
// try to write the asset to file.
assetRequest.state = .complete
// Move write off main thread.
DispatchQueue.global().async {
guard let downloadFolderPath = self.downloadFolderPath else {
owsFailDebug("Missing downloadFolderPath")
return
}
guard let asset = assetRequest.writeAssetToFile(downloadFolderPath: downloadFolderPath) else {
self.segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
self.assetRequestDidSucceed(assetRequest: assetRequest, asset: asset)
}
} else {
if !self.tryToCompleteRequest(assetRequest: assetRequest) {
self.processRequestQueueSync()
}
}
}
private func assetRequestDidSucceed(assetRequest: ProxiedContentAssetRequest, asset: ProxiedContentAsset) {
// Returns true if the request is completed.
private func tryToCompleteRequest(assetRequest: ProxiedContentAssetRequest) -> Bool {
AssertIsOnMainThread()
guard assetRequest.areAllSegmentsComplete() else {
return false
}
// If the asset request has completed all of its segments,
// try to write the asset to file.
assetRequest.state = .complete
// Move write off main thread.
DispatchQueue.global().async {
guard let downloadFolderPath = self.downloadFolderPath else {
owsFailDebug("Missing downloadFolderPath")
return
}
guard let asset = assetRequest.writeAssetToFile(downloadFolderPath: downloadFolderPath) else {
self.segmentRequestDidFail(assetRequest: assetRequest)
return
}
self.assetRequestDidSucceed(assetRequest: assetRequest, asset: asset)
}
return true
}
private func assetRequestDidSucceed(assetRequest: ProxiedContentAssetRequest, asset: ProxiedContentAsset) {
DispatchQueue.main.async {
self.assetMap.set(key: assetRequest.assetDescription.url, value: asset)
self.removeAssetRequestFromQueue(assetRequest: assetRequest)
@ -581,11 +600,14 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
}
}
// TODO: If we wanted to implement segment retry, we'll need to add
// a segmentRequestDidFail() method.
private func segmentRequestDidFail(assetRequest: ProxiedContentAssetRequest, assetSegment: ProxiedContentAssetSegment) {
private func segmentRequestDidFail(assetRequest: ProxiedContentAssetRequest, assetSegment: ProxiedContentAssetSegment? = nil) {
DispatchQueue.main.async {
assetSegment.state = .failed
if let assetSegment = assetSegment {
assetSegment.state = .failed
// TODO: If we wanted to implement segment retry, we'd do so here.
// For now, we just fail the entire asset request.
}
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest: assetRequest)
}
@ -652,17 +674,18 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
if assetRequest.state == .waiting {
// If asset request hasn't yet determined the resource size,
// try to do so now.
// try to do so now, by requesting a small initial segment.
assetRequest.state = .requestingSize
let segmentStart: UInt = 0
let segmentLength: UInt = ProxiedContentAssetRequest.smallestPossibleSegmentSize
// Vary the initial segment size to obscure the length of the response headers.
let segmentLength: UInt = 1024 + UInt(arc4random_uniform(1024))
var request = URLRequest(url: assetRequest.assetDescription.url as URL)
request.httpShouldUsePipelining = true
let rangeHeaderValue = "bytes=\(segmentStart)-\(segmentStart + segmentLength - 1)"
request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range")
let task = downloadSession.dataTask(with: request, completionHandler: { _, response, error -> Void in
self.handleAssetSizeResponse(assetRequest: assetRequest, response: response, error: error)
let task = downloadSession.dataTask(with: request, completionHandler: { data, response, error -> Void in
self.handleAssetSizeResponse(assetRequest: assetRequest, data: data, response: response, error: error)
})
assetRequest.contentLengthTask = task
@ -691,12 +714,19 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
processRequestQueueSync()
}
private func handleAssetSizeResponse(assetRequest: ProxiedContentAssetRequest, response: URLResponse?, error: Error?) {
private func handleAssetSizeResponse(assetRequest: ProxiedContentAssetRequest, data: Data?, response: URLResponse?, error: Error?) {
guard error == nil else {
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard let data = data,
data.count > 0 else {
owsFailDebug("Asset size response missing data.")
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard let httpResponse = response as? HTTPURLResponse else {
owsFailDebug("Asset size response is invalid.")
assetRequest.state = .failed
@ -744,8 +774,12 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
DispatchQueue.main.async {
assetRequest.contentLength = contentLength
assetRequest.createSegments(withInitialData: data)
assetRequest.state = .active
self.processRequestQueueSync()
if !self.tryToCompleteRequest(assetRequest: assetRequest) {
self.processRequestQueueSync()
}
}
}