parent
e9885af973
commit
789cea118d
|
@ -92,6 +92,7 @@
|
|||
34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EA1F3237260098030F /* OWSProfileManager.m */; };
|
||||
34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */; };
|
||||
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; };
|
||||
34D1F0521F7E8EA30066283D /* GifDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GifDownloader.swift */; };
|
||||
34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */; };
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
||||
34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; };
|
||||
|
@ -551,6 +552,7 @@
|
|||
34CE88EA1F3237260098030F /* OWSProfileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProfileManager.m; sourceTree = "<group>"; };
|
||||
34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFetcherJob.swift; sourceTree = "<group>"; };
|
||||
34D1F04F1F7D45A60066283D /* GifPickerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerCell.swift; sourceTree = "<group>"; };
|
||||
34D1F0511F7E8EA30066283D /* GifDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifDownloader.swift; sourceTree = "<group>"; };
|
||||
34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = "<group>"; };
|
||||
34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = "<group>"; };
|
||||
34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
|
@ -1425,6 +1427,7 @@
|
|||
76EB041D18170B33006006FC /* network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34D1F0511F7E8EA30066283D /* GifDownloader.swift */,
|
||||
3430FE171F7751D4000EC51B /* GifManager.swift */,
|
||||
B6B9ECFA198B31BA00C620D3 /* PushManager.h */,
|
||||
B6B9ECFB198B31BA00C620D3 /* PushManager.m */,
|
||||
|
@ -2309,6 +2312,7 @@
|
|||
45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */,
|
||||
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
|
||||
3448BFCF1EDF0EA7005B2D69 /* OWSMessagesComposerTextView.m in Sources */,
|
||||
34D1F0521F7E8EA30066283D /* GifDownloader.swift in Sources */,
|
||||
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */,
|
||||
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
|
||||
3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */,
|
||||
|
|
|
@ -77,7 +77,7 @@ class GifPickerCell: UICollectionViewCell {
|
|||
}
|
||||
Logger.verbose("\(TAG) picked rendition: \(rendition.name)")
|
||||
|
||||
assetRequest = GifManager.sharedInstance.downloadAssetAsync(rendition:rendition,
|
||||
assetRequest = GifDownloader.sharedInstance.downloadAssetAsync(rendition:rendition,
|
||||
success: { [weak self] asset in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.clearAssetRequest()
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ObjectiveC
|
||||
|
||||
@objc class GiphyAssetRequest: NSObject {
|
||||
static let TAG = "[GiphyAssetRequest]"
|
||||
|
||||
let rendition: GiphyRendition
|
||||
let success: ((GiphyAsset) -> Void)
|
||||
let failure: (() -> Void)
|
||||
var wasCancelled = false
|
||||
var assetFilePath: String?
|
||||
|
||||
init(rendition: GiphyRendition,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)
|
||||
) {
|
||||
self.rendition = rendition
|
||||
self.success = success
|
||||
self.failure = failure
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
wasCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
@objc class GiphyAsset: NSObject {
|
||||
static let TAG = "[GiphyAsset]"
|
||||
|
||||
let rendition: GiphyRendition
|
||||
let filePath: String
|
||||
|
||||
init(rendition: GiphyRendition,
|
||||
filePath: String) {
|
||||
self.rendition = rendition
|
||||
self.filePath = filePath
|
||||
}
|
||||
|
||||
deinit {
|
||||
let filePathCopy = filePath
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
try fileManager.removeItem(atPath:filePathCopy)
|
||||
} catch let error as NSError {
|
||||
owsFail("\(GiphyAsset.TAG) file cleanup failed: \(filePathCopy), \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var URLSessionTask_GiphyAssetRequest: UInt8 = 0
|
||||
|
||||
extension URLSessionTask {
|
||||
var assetRequest: GiphyAssetRequest {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &URLSessionTask_GiphyAssetRequest) as! GiphyAssetRequest
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &URLSessionTask_GiphyAssetRequest, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc class GifDownloader: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
static let TAG = "[GifDownloader]"
|
||||
|
||||
static let sharedInstance = GifDownloader()
|
||||
|
||||
private let operationQueue = OperationQueue()
|
||||
|
||||
// Force usage as a singleton
|
||||
override private init() {}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
private let kGiphyBaseURL = "https://api.giphy.com/"
|
||||
|
||||
private func giphyDownloadSession() -> URLSession? {
|
||||
// guard let baseUrl = NSURL(string:kGiphyBaseURL) else {
|
||||
// Logger.error("\(GifDownloader.TAG) Invalid base URL.")
|
||||
// return nil
|
||||
// }
|
||||
// TODO: Is this right?
|
||||
let configuration = URLSessionConfiguration.ephemeral
|
||||
// TODO: Is this right?
|
||||
configuration.connectionProxyDictionary = [
|
||||
kCFProxyHostNameKey as String: "giphy-proxy-production.whispersystems.org",
|
||||
kCFProxyPortNumberKey as String: "80",
|
||||
kCFProxyTypeKey as String: kCFProxyTypeHTTPS
|
||||
]
|
||||
configuration.urlCache = nil
|
||||
configuration.requestCachePolicy = .reloadIgnoringCacheData
|
||||
let session = URLSession(configuration:configuration, delegate:self, delegateQueue:operationQueue)
|
||||
return session
|
||||
// NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
|
||||
//
|
||||
// let sessionManager = AFHTTPSessionManager(baseURL:baseUrl as URL,
|
||||
// sessionConfiguration:sessionConf)
|
||||
// sessionManager.requestSerializer = AFJSONRequestSerializer()
|
||||
// sessionManager.responseSerializer = AFJSONResponseSerializer()
|
||||
//
|
||||
// return sessionManager
|
||||
}
|
||||
|
||||
// TODO: Use a proper cache.
|
||||
private var assetMap = [NSURL: GiphyAsset]()
|
||||
// TODO: We could use a proper queue.
|
||||
private var assetRequestQueue = [GiphyAssetRequest]()
|
||||
private var isDownloading = false
|
||||
|
||||
// The success and failure handlers are always called on main queue.
|
||||
// The success and failure handlers may be called synchronously on cache hit.
|
||||
public func downloadAssetAsync(rendition: GiphyRendition,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)) -> GiphyAssetRequest? {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
if let asset = assetMap[rendition.url] {
|
||||
success(asset)
|
||||
return nil
|
||||
}
|
||||
|
||||
let assetRequest = GiphyAssetRequest(rendition:rendition,
|
||||
success : { asset in
|
||||
DispatchQueue.main.async {
|
||||
self.assetMap[rendition.url] = asset
|
||||
success(asset)
|
||||
self.isDownloading = false
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
},
|
||||
failure : {
|
||||
DispatchQueue.main.async {
|
||||
failure()
|
||||
self.isDownloading = false
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
})
|
||||
assetRequestQueue.append(assetRequest)
|
||||
downloadIfNecessary()
|
||||
return assetRequest
|
||||
}
|
||||
|
||||
private func downloadIfNecessary() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard !self.isDownloading else {
|
||||
return
|
||||
}
|
||||
guard self.assetRequestQueue.count > 0 else {
|
||||
return
|
||||
}
|
||||
guard let assetRequest = self.assetRequestQueue.first else {
|
||||
owsFail("\(GiphyAsset.TAG) could not pop asset requests")
|
||||
return
|
||||
}
|
||||
self.assetRequestQueue.removeFirst()
|
||||
guard !assetRequest.wasCancelled else {
|
||||
DispatchQueue.main.async {
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
return
|
||||
}
|
||||
self.isDownloading = true
|
||||
|
||||
if let asset = self.assetMap[assetRequest.rendition.url] {
|
||||
// Deferred cache hit, avoids re-downloading assets already in the
|
||||
// asset cache.
|
||||
assetRequest.success(asset)
|
||||
return
|
||||
}
|
||||
|
||||
guard let downloadSession = self.giphyDownloadSession() else {
|
||||
Logger.error("\(GifDownloader.TAG) Couldn't create session manager.")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
|
||||
let task = downloadSession.downloadTask(with:assetRequest.rendition.url as URL)
|
||||
task.assetRequest = assetRequest
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: URLSessionDataDelegate
|
||||
|
||||
@nonobjc
|
||||
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
// MARK: URLSessionTaskDelegate
|
||||
|
||||
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
let assetRequest = task.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
task.cancel()
|
||||
return
|
||||
}
|
||||
if let error = error {
|
||||
Logger.error("\(GifDownloader.TAG) download failed with error: \(error)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
guard let httpResponse = task.response as? HTTPURLResponse else {
|
||||
Logger.error("\(GifDownloader.TAG) missing or unexpected response: \(task.response)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
let statusCode = httpResponse.statusCode
|
||||
guard statusCode >= 200 && statusCode < 400 else {
|
||||
Logger.error("\(GifDownloader.TAG) response has invalid status code: \(statusCode)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
guard let assetFilePath = assetRequest.assetFilePath else {
|
||||
Logger.error("\(GifDownloader.TAG) task is missing asset file")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
Logger.verbose("\(GifDownloader.TAG) download succeeded: \(assetRequest.rendition.url)")
|
||||
let asset = GiphyAsset(rendition: assetRequest.rendition, filePath : assetFilePath)
|
||||
assetRequest.success(asset)
|
||||
}
|
||||
|
||||
// MARK: URLSessionDownloadDelegate
|
||||
|
||||
private func fileExtension(forFormat format: GiphyFormat) -> String {
|
||||
switch format {
|
||||
case .gif:
|
||||
return "gif"
|
||||
case .webp:
|
||||
return "webp"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
let dirPath = NSTemporaryDirectory()
|
||||
let fileExtension = self.fileExtension(forFormat:assetRequest.rendition.format)
|
||||
let fileName = (NSUUID().uuidString as NSString).appendingPathExtension(fileExtension)!
|
||||
let filePath = (dirPath as NSString).appendingPathComponent(fileName)
|
||||
|
||||
do {
|
||||
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath:filePath))
|
||||
assetRequest.assetFilePath = filePath
|
||||
} catch let error as NSError {
|
||||
owsFail("\(GiphyAsset.TAG) file move failed from: \(location), to: \(filePath), \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,6 +61,10 @@ enum GiphyFormat {
|
|||
else {
|
||||
continue
|
||||
}
|
||||
guard !rendition.name.hasSuffix("_downsampled")
|
||||
else {
|
||||
continue
|
||||
}
|
||||
guard rendition.width >= kMinDimension &&
|
||||
rendition.width <= kMaxDimension &&
|
||||
rendition.height >= kMinDimension &&
|
||||
|
@ -83,68 +87,7 @@ enum GiphyFormat {
|
|||
}
|
||||
}
|
||||
|
||||
@objc class GiphyAssetRequest: NSObject {
|
||||
static let TAG = "[GiphyAssetRequest]"
|
||||
|
||||
let rendition: GiphyRendition
|
||||
let success: ((GiphyAsset) -> Void)
|
||||
let failure: (() -> Void)
|
||||
var wasCancelled = false
|
||||
var assetFilePath: String?
|
||||
|
||||
init(rendition: GiphyRendition,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)
|
||||
) {
|
||||
self.rendition = rendition
|
||||
self.success = success
|
||||
self.failure = failure
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
wasCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
@objc class GiphyAsset: NSObject {
|
||||
static let TAG = "[GiphyAsset]"
|
||||
|
||||
let rendition: GiphyRendition
|
||||
let filePath: String
|
||||
|
||||
init(rendition: GiphyRendition,
|
||||
filePath: String) {
|
||||
self.rendition = rendition
|
||||
self.filePath = filePath
|
||||
}
|
||||
|
||||
deinit {
|
||||
let filePathCopy = filePath
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
try fileManager.removeItem(atPath:filePathCopy)
|
||||
} catch let error as NSError {
|
||||
owsFail("\(GiphyAsset.TAG) file cleanup failed: \(filePathCopy), \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var URLSessionTask_GiphyAssetRequest: UInt8 = 0
|
||||
|
||||
extension URLSessionTask {
|
||||
var assetRequest: GiphyAssetRequest {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &URLSessionTask_GiphyAssetRequest) as! GiphyAssetRequest
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &URLSessionTask_GiphyAssetRequest, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc class GifManager: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
|
||||
@objc class GifManager: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
@ -152,8 +95,6 @@ extension URLSessionTask {
|
|||
|
||||
static let sharedInstance = GifManager()
|
||||
|
||||
private let operationQueue = OperationQueue()
|
||||
|
||||
// Force usage as a singleton
|
||||
override private init() {}
|
||||
|
||||
|
@ -185,33 +126,6 @@ extension URLSessionTask {
|
|||
return sessionManager
|
||||
}
|
||||
|
||||
private func giphyDownloadSession() -> URLSession? {
|
||||
// guard let baseUrl = NSURL(string:kGiphyBaseURL) else {
|
||||
// Logger.error("\(GifManager.TAG) Invalid base URL.")
|
||||
// return nil
|
||||
// }
|
||||
// TODO: Is this right?
|
||||
let configuration = URLSessionConfiguration.ephemeral
|
||||
// TODO: Is this right?
|
||||
configuration.connectionProxyDictionary = [
|
||||
kCFProxyHostNameKey as String: "giphy-proxy-production.whispersystems.org",
|
||||
kCFProxyPortNumberKey as String: "80",
|
||||
kCFProxyTypeKey as String: kCFProxyTypeHTTPS
|
||||
]
|
||||
configuration.urlCache = nil
|
||||
configuration.requestCachePolicy = .reloadIgnoringCacheData
|
||||
let session = URLSession(configuration:configuration, delegate:self, delegateQueue:operationQueue)
|
||||
return session
|
||||
// NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
|
||||
//
|
||||
// let sessionManager = AFHTTPSessionManager(baseURL:baseUrl as URL,
|
||||
// sessionConfiguration:sessionConf)
|
||||
// sessionManager.requestSerializer = AFJSONRequestSerializer()
|
||||
// sessionManager.responseSerializer = AFJSONResponseSerializer()
|
||||
//
|
||||
// return sessionManager
|
||||
}
|
||||
|
||||
// TODO:
|
||||
public func test() {
|
||||
search(query:"monkey",
|
||||
|
@ -288,6 +202,7 @@ extension URLSessionTask {
|
|||
return result
|
||||
}
|
||||
|
||||
// Giphy API results are often incomplete or malformed, so we need to be defensive.
|
||||
private func parseGiphyImage(imageDict: [String:Any]) -> GiphyImageInfo? {
|
||||
guard let giphyId = imageDict["id"] as? String else {
|
||||
Logger.warn("\(GifManager.TAG) Image dict missing id.")
|
||||
|
@ -338,6 +253,7 @@ extension URLSessionTask {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Giphy API results are often incomplete or malformed, so we need to be defensive.
|
||||
private func parseGiphyRendition(renditionName: String,
|
||||
renditionDict: [String:Any]) -> GiphyRendition? {
|
||||
guard let width = parsePositiveUInt(dict:renditionDict, key:"width", typeName:"rendition") else {
|
||||
|
@ -381,8 +297,6 @@ extension URLSessionTask {
|
|||
)
|
||||
}
|
||||
|
||||
// Giphy API results are often incompl
|
||||
//
|
||||
// {
|
||||
// height = 65;
|
||||
// mp4 = "https://media3.giphy.com/media/42YlR8u9gV5Cw/100w.mp4";
|
||||
|
@ -412,178 +326,4 @@ extension URLSessionTask {
|
|||
}
|
||||
return parsedValue
|
||||
}
|
||||
|
||||
// MARK: Rendition Download
|
||||
|
||||
// TODO: Use a proper cache.
|
||||
private var assetMap = [NSURL: GiphyAsset]()
|
||||
// TODO: We could use a proper queue.
|
||||
private var assetRequestQueue = [GiphyAssetRequest]()
|
||||
private var isDownloading = false
|
||||
|
||||
// The success and failure handlers are always called on main queue.
|
||||
// The success and failure handlers may be called synchronously on cache hit.
|
||||
public func downloadAssetAsync(rendition: GiphyRendition,
|
||||
success:@escaping ((GiphyAsset) -> Void),
|
||||
failure:@escaping (() -> Void)) -> GiphyAssetRequest? {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
if let asset = assetMap[rendition.url] {
|
||||
success(asset)
|
||||
return nil
|
||||
}
|
||||
|
||||
let assetRequest = GiphyAssetRequest(rendition:rendition,
|
||||
success : { asset in
|
||||
DispatchQueue.main.async {
|
||||
self.assetMap[rendition.url] = asset
|
||||
success(asset)
|
||||
self.isDownloading = false
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
},
|
||||
failure : {
|
||||
DispatchQueue.main.async {
|
||||
failure()
|
||||
self.isDownloading = false
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
})
|
||||
assetRequestQueue.append(assetRequest)
|
||||
downloadIfNecessary()
|
||||
return assetRequest
|
||||
}
|
||||
|
||||
private func downloadIfNecessary() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard !self.isDownloading else {
|
||||
return
|
||||
}
|
||||
guard self.assetRequestQueue.count > 0 else {
|
||||
return
|
||||
}
|
||||
guard let assetRequest = self.assetRequestQueue.first else {
|
||||
owsFail("\(GiphyAsset.TAG) could not pop asset requests")
|
||||
return
|
||||
}
|
||||
self.assetRequestQueue.removeFirst()
|
||||
guard !assetRequest.wasCancelled else {
|
||||
DispatchQueue.main.async {
|
||||
self.downloadIfNecessary()
|
||||
}
|
||||
return
|
||||
}
|
||||
self.isDownloading = true
|
||||
|
||||
if let asset = self.assetMap[assetRequest.rendition.url] {
|
||||
// Deferred cache hit, avoids re-downloading assets already in the
|
||||
// asset cache.
|
||||
assetRequest.success(asset)
|
||||
return
|
||||
}
|
||||
|
||||
guard let downloadSession = self.giphyDownloadSession() else {
|
||||
Logger.error("\(GifManager.TAG) Couldn't create session manager.")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
|
||||
let task = downloadSession.downloadTask(with:assetRequest.rendition.url as URL)
|
||||
task.assetRequest = assetRequest
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: URLSessionDataDelegate
|
||||
|
||||
@nonobjc
|
||||
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
// MARK: URLSessionTaskDelegate
|
||||
|
||||
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
let assetRequest = task.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
task.cancel()
|
||||
return
|
||||
}
|
||||
if let error = error {
|
||||
Logger.error("\(GifManager.TAG) download failed with error: \(error)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
guard let httpResponse = task.response as? HTTPURLResponse else {
|
||||
Logger.error("\(GifManager.TAG) missing or unexpected response: \(task.response)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
let statusCode = httpResponse.statusCode
|
||||
guard statusCode >= 200 && statusCode < 400 else {
|
||||
Logger.error("\(GifManager.TAG) response has invalid status code: \(statusCode)")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
guard let assetFilePath = assetRequest.assetFilePath else {
|
||||
Logger.error("\(GifManager.TAG) task is missing asset file")
|
||||
assetRequest.failure()
|
||||
return
|
||||
}
|
||||
Logger.verbose("\(GifManager.TAG) download succeeded: \(assetRequest.rendition.url)")
|
||||
let asset = GiphyAsset(rendition: assetRequest.rendition, filePath : assetFilePath)
|
||||
assetRequest.success(asset)
|
||||
}
|
||||
|
||||
// MARK: URLSessionDownloadDelegate
|
||||
|
||||
private func fileExtension(forFormat format: GiphyFormat) -> String {
|
||||
switch format {
|
||||
case .gif:
|
||||
return "gif"
|
||||
case .webp:
|
||||
return "webp"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
let dirPath = NSTemporaryDirectory()
|
||||
let fileExtension = self.fileExtension(forFormat:assetRequest.rendition.format)
|
||||
let fileName = (NSUUID().uuidString as NSString).appendingPathExtension(fileExtension)!
|
||||
let filePath = (dirPath as NSString).appendingPathComponent(fileName)
|
||||
|
||||
do {
|
||||
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath:filePath))
|
||||
assetRequest.assetFilePath = filePath
|
||||
} catch let error as NSError {
|
||||
owsFail("\(GiphyAsset.TAG) file move failed from: \(location), to: \(filePath), \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
|
||||
let assetRequest = downloadTask.assetRequest
|
||||
guard !assetRequest.wasCancelled else {
|
||||
downloadTask.cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue