This included: - Removing unavailable inits wholesale if no longer `required` - Marking a few classes `final` so they could continue using `Self(...)` rather than `OWSWhatever(...)`
126 lines
4.3 KiB
Swift
126 lines
4.3 KiB
Swift
//
|
|
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
class DownloadStickerPackOperation: CDNDownloadOperation {
|
|
|
|
private let stickerPackInfo: StickerPackInfo
|
|
private let success: (StickerPack) -> Void
|
|
private let failure: (Error) -> Void
|
|
|
|
@objc
|
|
public init(
|
|
stickerPackInfo: StickerPackInfo,
|
|
success: @escaping (StickerPack) -> Void,
|
|
failure: @escaping (Error) -> Void
|
|
) {
|
|
owsAssertDebug(stickerPackInfo.packId.count > 0)
|
|
owsAssertDebug(stickerPackInfo.packKey.count > 0)
|
|
|
|
self.stickerPackInfo = stickerPackInfo
|
|
self.success = success
|
|
self.failure = failure
|
|
|
|
super.init()
|
|
}
|
|
|
|
override public func run() {
|
|
|
|
if let stickerPack = StickerManager.fetchStickerPack(stickerPackInfo: stickerPackInfo) {
|
|
success(stickerPack)
|
|
self.reportSuccess()
|
|
return
|
|
}
|
|
|
|
// https://cdn.signal.org/stickers/<pack_id>/manifest.proto
|
|
let urlPath = "stickers/\(stickerPackInfo.packId.hexadecimalString)/manifest.proto"
|
|
|
|
firstly {
|
|
try tryToDownload(urlPath: urlPath, maxDownloadSize: kMaxStickerPackDownloadSize)
|
|
}.done(on: DispatchQueue.global()) { [weak self] (url: URL) in
|
|
guard let self = self else {
|
|
return
|
|
}
|
|
|
|
do {
|
|
let url = try StickerManager.decrypt(at: url, packKey: self.stickerPackInfo.packKey)
|
|
let plaintext = try Data(contentsOf: url)
|
|
|
|
let stickerPack = try self.parseStickerPackManifest(stickerPackInfo: self.stickerPackInfo,
|
|
manifestData: plaintext)
|
|
|
|
self.success(stickerPack)
|
|
self.reportSuccess()
|
|
} catch {
|
|
owsFailDebug("Decryption failed: \(error)")
|
|
|
|
self.markUrlPathAsCorrupt(urlPath)
|
|
|
|
// Fail immediately; do not retry.
|
|
return self.reportError(SSKUnretryableError.stickerDecryptionFailure)
|
|
}
|
|
}.catch(on: DispatchQueue.global()) { [weak self] error in
|
|
guard let self = self else {
|
|
return
|
|
}
|
|
if error.hasFatalHttpStatusCode() {
|
|
StickerManager.markStickerPackAsMissing(stickerPackInfo: self.stickerPackInfo)
|
|
}
|
|
return self.reportError(withUndefinedRetry: error)
|
|
}
|
|
}
|
|
|
|
private func parseStickerPackManifest(stickerPackInfo: StickerPackInfo,
|
|
manifestData: Data) throws -> StickerPack {
|
|
owsAssertDebug(manifestData.count > 0)
|
|
|
|
let manifestProto: SSKProtoPack
|
|
do {
|
|
manifestProto = try SSKProtoPack(serializedData: manifestData)
|
|
} catch let error as NSError {
|
|
owsFailDebug("Couldn't parse protos: \(error)")
|
|
throw StickerError.invalidInput
|
|
}
|
|
let title = parseOptionalString(manifestProto.title)
|
|
let author = parseOptionalString(manifestProto.author)
|
|
let manifestCover = parsePackItem(manifestProto.cover)
|
|
var items = [StickerPackItem]()
|
|
for stickerProto in manifestProto.stickers {
|
|
if let item = parsePackItem(stickerProto) {
|
|
items.append(item)
|
|
}
|
|
}
|
|
guard let firstItem = items.first else {
|
|
owsFailDebug("Invalid manifest, no stickers")
|
|
throw StickerError.invalidInput
|
|
}
|
|
let cover = manifestCover ?? firstItem
|
|
|
|
let stickerPack = StickerPack(info: stickerPackInfo, title: title, author: author, cover: cover, stickers: items)
|
|
return stickerPack
|
|
}
|
|
|
|
private func parseOptionalString(_ value: String?) -> String? {
|
|
value?.ows_stripped().nilIfEmpty
|
|
}
|
|
|
|
private func parsePackItem(_ proto: SSKProtoPackSticker?) -> StickerPackItem? {
|
|
guard let proto = proto else {
|
|
return nil
|
|
}
|
|
let stickerId = proto.id
|
|
let emojiString = parseOptionalString(proto.emoji) ?? ""
|
|
let contentType = parseOptionalString(proto.contentType) ?? ""
|
|
return StickerPackItem(stickerId: stickerId, emojiString: emojiString, contentType: contentType)
|
|
}
|
|
|
|
override public func didFail(error: Error) {
|
|
Logger.error("Download exhausted retries: \(error)")
|
|
|
|
failure(error)
|
|
}
|
|
}
|