Signal-iOS/SignalServiceKit/Messages/Stickers/DownloadStickerPackOperation.swift
Jordan Rose b0de59f2e2 Remove required from every init that is not dynamically dispatched
This included:
- Removing unavailable inits wholesale if no longer `required`
- Marking a few classes `final` so they could continue using
  `Self(...)` rather than `OWSWhatever(...)`
2024-04-01 15:27:20 -07:00

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)
}
}