Uncodegen StickerPack

This commit is contained in:
Max Radermacher 2026-02-04 15:10:26 -06:00 committed by GitHub
parent a96689a6c6
commit 24643f3386
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 316 additions and 956 deletions

View File

@ -672,6 +672,7 @@
503C2F432977752B00217527 /* OWSURLSessionEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503C2F422977752B00217527 /* OWSURLSessionEndpoint.swift */; };
50423CA42BBF427900DCB8F5 /* StaleProfileFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50423CA32BBF427900DCB8F5 /* StaleProfileFetcher.swift */; };
504271B62BB4C54500E33C01 /* SystemContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504271B52BB4C54500E33C01 /* SystemContact.swift */; };
50428EA52F2BD81700B0C969 /* StickerPackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50428EA42F2BD81700B0C969 /* StickerPackItem.swift */; };
5042EAA3287F96FB00C9B19F /* VisibleBadgeResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5042EAA2287F96FB00C9B19F /* VisibleBadgeResolverTest.swift */; };
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */; };
50438A8F2ECBBE1D00FCB28F /* BackupRefreshManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */; };
@ -745,6 +746,7 @@
507C07402F116E9200ECFEFA /* NormalizedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507C073F2F116E9200ECFEFA /* NormalizedImage.swift */; };
507C6AF52ED10BD900A7C74C /* RegisteredState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507C6AF42ED10BD900A7C74C /* RegisteredState.swift */; };
507CD5E529660D5100E47DAC /* ServiceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CD5E429660D5100E47DAC /* ServiceId.swift */; };
507F4F0E2F2A86F8000E6D7D /* StickerPackRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507F4F0D2F2A86F8000E6D7D /* StickerPackRecord.swift */; };
508622AD2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508622AC2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift */; };
508C72242C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508C72232C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift */; };
508F0346296F72F4001D88D0 /* CustomCellBackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508F0345296F72F4001D88D0 /* CustomCellBackgroundColor.swift */; };
@ -3595,14 +3597,11 @@
F9C5CC0B289453B300548EEE /* StickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C919289453B100548EEE /* StickerManager.swift */; };
F9C5CC0C289453B300548EEE /* CDNDownloadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C91A289453B100548EEE /* CDNDownloadOperation.swift */; };
F9C5CC0D289453B300548EEE /* StickerMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C91B289453B100548EEE /* StickerMetadata.swift */; };
F9C5CC0E289453B300548EEE /* StickerPack+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C91C289453B100548EEE /* StickerPack+SDS.swift */; };
F9C5CC0F289453B300548EEE /* StickerPack.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C5C91D289453B100548EEE /* StickerPack.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9C5CC11289453B300548EEE /* DefaultStickers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C91F289453B100548EEE /* DefaultStickers.swift */; };
F9C5CC12289453B300548EEE /* InstalledSticker+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C920289453B100548EEE /* InstalledSticker+SDS.swift */; };
F9C5CC13289453B300548EEE /* StickerInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C921289453B100548EEE /* StickerInfo.m */; };
F9C5CC14289453B300548EEE /* DownloadStickerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C922289453B100548EEE /* DownloadStickerOperation.swift */; };
F9C5CC15289453B300548EEE /* InstalledSticker.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C923289453B100548EEE /* InstalledSticker.m */; };
F9C5CC17289453B300548EEE /* StickerPack.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C925289453B100548EEE /* StickerPack.m */; };
F9C5CC18289453B300548EEE /* DownloadStickerPackOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C926289453B100548EEE /* DownloadStickerPackOperation.swift */; };
F9C5CC19289453B300548EEE /* MessageSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C927289453B100548EEE /* MessageSticker.swift */; };
F9C5CC1B289453B300548EEE /* StickerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C929289453B100548EEE /* StickerError.swift */; };
@ -4835,6 +4834,7 @@
503C2F422977752B00217527 /* OWSURLSessionEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSURLSessionEndpoint.swift; sourceTree = "<group>"; };
50423CA32BBF427900DCB8F5 /* StaleProfileFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaleProfileFetcher.swift; sourceTree = "<group>"; };
504271B52BB4C54500E33C01 /* SystemContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemContact.swift; sourceTree = "<group>"; };
50428EA42F2BD81700B0C969 /* StickerPackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackItem.swift; sourceTree = "<group>"; };
5042EAA2287F96FB00C9B19F /* VisibleBadgeResolverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBadgeResolverTest.swift; sourceTree = "<group>"; };
50468F2829EE130A00948E02 /* InteractionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionStore.swift; sourceTree = "<group>"; };
50468F2A29EE19C300948E02 /* PhoneNumberChangedMessageInserterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberChangedMessageInserterTest.swift; sourceTree = "<group>"; };
@ -4900,6 +4900,7 @@
507D614B2BE433EE00DA7BA3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = translations/be.lproj/InfoPlist.strings; sourceTree = "<group>"; };
507D614C2BE433EE00DA7BA3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = translations/be.lproj/Localizable.strings; sourceTree = "<group>"; };
507D614D2BE433EE00DA7BA3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = be; path = translations/be.lproj/PluralAware.stringsdict; sourceTree = "<group>"; };
507F4F0D2F2A86F8000E6D7D /* StickerPackRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackRecord.swift; sourceTree = "<group>"; };
508347052AABBF9900DD2EC0 /* ProfileManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManagerTest.swift; sourceTree = "<group>"; };
508622AC2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanonicalPhoneNumberTest.swift; sourceTree = "<group>"; };
508C72232C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSOutgoingResendResponseTest.swift; sourceTree = "<group>"; };
@ -7794,14 +7795,11 @@
F9C5C919289453B100548EEE /* StickerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManager.swift; sourceTree = "<group>"; };
F9C5C91A289453B100548EEE /* CDNDownloadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNDownloadOperation.swift; sourceTree = "<group>"; };
F9C5C91B289453B100548EEE /* StickerMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerMetadata.swift; sourceTree = "<group>"; };
F9C5C91C289453B100548EEE /* StickerPack+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StickerPack+SDS.swift"; sourceTree = "<group>"; };
F9C5C91D289453B100548EEE /* StickerPack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StickerPack.h; sourceTree = "<group>"; };
F9C5C91F289453B100548EEE /* DefaultStickers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultStickers.swift; sourceTree = "<group>"; };
F9C5C920289453B100548EEE /* InstalledSticker+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "InstalledSticker+SDS.swift"; sourceTree = "<group>"; };
F9C5C921289453B100548EEE /* StickerInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StickerInfo.m; sourceTree = "<group>"; };
F9C5C922289453B100548EEE /* DownloadStickerOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadStickerOperation.swift; sourceTree = "<group>"; };
F9C5C923289453B100548EEE /* InstalledSticker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstalledSticker.m; sourceTree = "<group>"; };
F9C5C925289453B100548EEE /* StickerPack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StickerPack.m; sourceTree = "<group>"; };
F9C5C926289453B100548EEE /* DownloadStickerPackOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadStickerPackOperation.swift; sourceTree = "<group>"; };
F9C5C927289453B100548EEE /* MessageSticker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSticker.swift; sourceTree = "<group>"; };
F9C5C929289453B100548EEE /* StickerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerError.swift; sourceTree = "<group>"; };
@ -14545,10 +14543,9 @@
F9C5C921289453B100548EEE /* StickerInfo.m */,
F9C5C919289453B100548EEE /* StickerManager.swift */,
F9C5C91B289453B100548EEE /* StickerMetadata.swift */,
F9C5C91C289453B100548EEE /* StickerPack+SDS.swift */,
F9C5C91D289453B100548EEE /* StickerPack.h */,
F9C5C925289453B100548EEE /* StickerPack.m */,
F9479EF7293D212C003B3503 /* StickerPackInfo.swift */,
50428EA42F2BD81700B0C969 /* StickerPackItem.swift */,
507F4F0D2F2A86F8000E6D7D /* StickerPackRecord.swift */,
);
path = Stickers;
sourceTree = "<group>";
@ -15303,7 +15300,6 @@
F9C5C8A7289451B900548EEE /* SignalServiceKit.h in Headers */,
F9C5CD2C289453B300548EEE /* SSKAccessors+SDS.h in Headers */,
F9C5CC1C289453B300548EEE /* StickerInfo.h in Headers */,
F9C5CC0F289453B300548EEE /* StickerPack.h in Headers */,
668A01142C2B6077007B8808 /* Threading.h in Headers */,
F9C5CBC8289453B300548EEE /* TSCall.h in Headers */,
F9C5CCD1289453B300548EEE /* TSContactThread.h in Headers */,
@ -19391,9 +19387,9 @@
F9C5CC13289453B300548EEE /* StickerInfo.m in Sources */,
F9C5CC0B289453B300548EEE /* StickerManager.swift in Sources */,
F9C5CC0D289453B300548EEE /* StickerMetadata.swift in Sources */,
F9C5CC0E289453B300548EEE /* StickerPack+SDS.swift in Sources */,
F9C5CC17289453B300548EEE /* StickerPack.m in Sources */,
F9479EF8293D212C003B3503 /* StickerPackInfo.swift in Sources */,
50428EA52F2BD81700B0C969 /* StickerPackItem.swift in Sources */,
507F4F0E2F2A86F8000E6D7D /* StickerPackRecord.swift in Sources */,
500CC1EB2F15D1E5001A86F7 /* StickerPackSyncMessage.swift in Sources */,
725465532BA0282D00EABFD2 /* StorageService+GroupsV2.swift in Sources */,
F9C5CCA3289453B300548EEE /* StorageService.pb.swift in Sources */,

View File

@ -140,8 +140,8 @@ public class ManageStickersViewController: OWSTableViewController2 {
updateMapWithOldSources(&oldTransientSources, availableBuiltInStickerPackSources)
updateMapWithOldSources(&oldTransientSources, knownStickerPackSources)
var installedStickerPacks = [StickerPack]()
var availableBuiltInStickerPacks = [StickerPack]()
var installedStickerPacks = [StickerPackRecord]()
var availableBuiltInStickerPacks = [StickerPackRecord]()
var availableKnownStickerPacksFromMessages = [DatedStickerPackInfo]()
SSKEnvironment.shared.databaseStorageRef.read { transaction in
let allPacks = StickerManager.allStickerPacks(transaction: transaction)
@ -521,7 +521,7 @@ public class ManageStickersViewController: OWSTableViewController2 {
self.sendMessageFlow = sendMessageFlow
}
private func install(stickerPack: StickerPack) {
private func install(stickerPack: StickerPackRecord) {
AssertIsOnMainThread()
let modalVC = ModalActivityIndicatorViewController(canCancel: false, presentationDelay: 0)

View File

@ -60,7 +60,7 @@ public class BackupArchiveStickerPackArchiver: BackupArchiveProtoStreamWriter {
var handledPacks = Set<Data>()
func archiveInstalledStickerPack(
_ installedStickerPack: StickerPack,
_ installedStickerPack: StickerPackRecord,
_ frameBencher: BackupArchive.Bencher.FrameBencher,
) {
autoreleasepool {
@ -88,12 +88,11 @@ public class BackupArchiveStickerPackArchiver: BackupArchiveProtoStreamWriter {
}
}
func enumerateStickerPackRecord(tx: DBReadTransaction, block: (StickerPack) throws -> Void) throws {
func enumerateStickerPackRecord(tx: DBReadTransaction, block: (StickerPackRecord) throws -> Void) throws {
let cursor = try StickerPackRecord
.filter(Column(StickerPackRecord.CodingKeys.isInstalled) == true)
.fetchCursor(tx.database)
while let next = try cursor.next() {
let stickerPack = try StickerPack.fromRecord(next)
while let stickerPack = try cursor.next() {
try block(stickerPack)
}
}

View File

@ -6,13 +6,13 @@
import Foundation
enum DownloadStickerPackOperation {
static func run(stickerPackInfo: StickerPackInfo) async throws -> StickerPack {
static func run(stickerPackInfo: StickerPackInfo) async throws -> StickerPackRecord {
return try await Retry.performWithBackoff(maxAttempts: 4) {
return try await self._run(stickerPackInfo: stickerPackInfo)
}
}
private static func _run(stickerPackInfo: StickerPackInfo) async throws -> StickerPack {
private static func _run(stickerPackInfo: StickerPackInfo) async throws -> StickerPackRecord {
owsAssertDebug(stickerPackInfo.packId.count > 0)
owsAssertDebug(stickerPackInfo.packKey.count > 0)
@ -54,7 +54,7 @@ enum DownloadStickerPackOperation {
}
}
private static func parseStickerPackManifest(stickerPackInfo: StickerPackInfo, manifestData: Data) throws -> StickerPack {
private static func parseStickerPackManifest(stickerPackInfo: StickerPackInfo, manifestData: Data) throws -> StickerPackRecord {
owsAssertDebug(manifestData.count > 0)
let manifestProto: SSKProtoPack
@ -79,8 +79,7 @@ enum DownloadStickerPackOperation {
}
let cover = manifestCover ?? firstItem
let stickerPack = StickerPack(info: stickerPackInfo, title: title, author: author, cover: cover, stickers: items)
return stickerPack
return StickerPackRecord(info: stickerPackInfo, title: title, author: author, cover: cover, items: items)
}
private static func parseOptionalString(_ value: String?) -> String? {

View File

@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) NSData *packKey;
@property (nonatomic, readonly) UInt32 stickerId;
- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey stickerId:(UInt32)stickerId;
- (instancetype)initWithPackId:(nullable NSData *)packId packKey:(nullable NSData *)packKey stickerId:(UInt32)stickerId;
- (NSString *)asKey;
+ (NSString *)keyWithPackId:(NSData *)packId stickerId:(UInt32)stickerId;

View File

@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation StickerInfo
- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey stickerId:(UInt32)stickerId
- (instancetype)initWithPackId:(nullable NSData *)packId packKey:(nullable NSData *)packKey stickerId:(UInt32)stickerId
{
self = [super init];

View File

@ -160,19 +160,19 @@ public class StickerManager: NSObject {
// MARK: - Sticker Packs
public class func allStickerPacks() -> [StickerPack] {
var result = [StickerPack]()
public class func allStickerPacks() -> [StickerPackRecord] {
var result = [StickerPackRecord]()
SSKEnvironment.shared.databaseStorageRef.read { transaction in
result += allStickerPacks(transaction: transaction)
}
return result
}
public class func allStickerPacks(transaction: DBReadTransaction) -> [StickerPack] {
return StickerPack.anyFetchAll(transaction: transaction)
public class func allStickerPacks(transaction: DBReadTransaction) -> [StickerPackRecord] {
return StickerPackRecord.anyFetchAll(transaction: transaction)
}
public class func installedStickerPacks(transaction: DBReadTransaction) -> [StickerPack] {
public class func installedStickerPacks(transaction: DBReadTransaction) -> [StickerPackRecord] {
return allStickerPacks(transaction: transaction).filter {
$0.isInstalled
}
@ -204,7 +204,7 @@ public class StickerManager: NSObject {
if shouldRemove {
uninstallSticker(stickerInfo: stickerPack.coverInfo, transaction: transaction)
for stickerInfo in stickerPack.stickerInfos {
for stickerInfo in stickerPack.stickerInfos() {
if stickerInfo == stickerPack.coverInfo {
// Don't uninstall the cover for saved packs.
continue
@ -214,7 +214,7 @@ public class StickerManager: NSObject {
stickerPack.anyRemove(transaction: transaction)
} else {
stickerPack.update(withIsInstalled: false, transaction: transaction)
stickerPack.updateWith(isInstalled: false, tx: transaction)
}
if wasLocallyInitiated {
@ -231,7 +231,7 @@ public class StickerManager: NSObject {
}
public class func installStickerPack(
stickerPack: StickerPack,
stickerPack: StickerPackRecord,
wasLocallyInitiated: Bool,
transaction: DBWriteTransaction,
) {
@ -243,15 +243,15 @@ public class StickerManager: NSObject {
)
}
public class func fetchStickerPack(stickerPackInfo: StickerPackInfo) -> StickerPack? {
public class func fetchStickerPack(stickerPackInfo: StickerPackInfo) -> StickerPackRecord? {
return SSKEnvironment.shared.databaseStorageRef.read { transaction in
return fetchStickerPack(stickerPackInfo: stickerPackInfo, transaction: transaction)
}
}
public class func fetchStickerPack(stickerPackInfo: StickerPackInfo, transaction: DBReadTransaction) -> StickerPack? {
let uniqueId = StickerPack.uniqueId(for: stickerPackInfo)
return StickerPack.anyFetch(uniqueId: uniqueId, transaction: transaction)
public class func fetchStickerPack(stickerPackInfo: StickerPackInfo, transaction: DBReadTransaction) -> StickerPackRecord? {
let uniqueId = StickerPackRecord.uniqueId(forStickerPackInfo: stickerPackInfo)
return StickerPackRecord.anyFetch(uniqueId: uniqueId, transaction: transaction)
}
private class func tryToDownloadAndSaveStickerPack(
@ -270,7 +270,7 @@ public class StickerManager: NSObject {
private let packOperationQueue = ConcurrentTaskQueue(concurrentLimit: 3)
private func tryToDownloadStickerPack(stickerPackInfo: StickerPackInfo) -> Promise<StickerPack> {
private func tryToDownloadStickerPack(stickerPackInfo: StickerPackInfo) -> Promise<StickerPackRecord> {
return Promise.wrapAsync { [packOperationQueue] in
return try await packOperationQueue.run {
return try await DownloadStickerPackOperation.run(stickerPackInfo: stickerPackInfo)
@ -279,12 +279,12 @@ public class StickerManager: NSObject {
}
// This method is public so that we can download "transient" (uninstalled) sticker packs.
public class func tryToDownloadStickerPack(stickerPackInfo: StickerPackInfo) -> Promise<StickerPack> {
public class func tryToDownloadStickerPack(stickerPackInfo: StickerPackInfo) -> Promise<StickerPackRecord> {
return SSKEnvironment.shared.stickerManagerRef.tryToDownloadStickerPack(stickerPackInfo: stickerPackInfo)
}
private class func upsertStickerPack(
stickerPack: StickerPack,
stickerPack: StickerPackRecord,
installMode: InstallMode,
wasLocallyInitiated: Bool,
) {
@ -299,7 +299,7 @@ public class StickerManager: NSObject {
}
private class func upsertStickerPack(
stickerPack stickerPackParam: StickerPack,
stickerPack stickerPackParam: StickerPackRecord,
installMode: InstallMode,
wasLocallyInitiated: Bool,
transaction: DBWriteTransaction,
@ -307,15 +307,15 @@ public class StickerManager: NSObject {
// If we re-insert a sticker pack, make sure that it
// has a new row id.
_ = stickerPackParam as NSCopying
let stickerPack = stickerPackParam.copy() as! StickerPack
stickerPack.clearRowId()
let stickerPack = stickerPackParam.copy() as! StickerPackRecord
stickerPack.id = nil
let oldCopy = fetchStickerPack(stickerPackInfo: stickerPack.info, transaction: transaction)
let wasSaved = oldCopy != nil
// Preserve old mutable state.
if let oldCopy {
stickerPack.update(withIsInstalled: oldCopy.isInstalled, transaction: transaction)
stickerPack.updateWith(isInstalled: oldCopy.isInstalled, tx: transaction)
} else {
stickerPack.anyInsert(transaction: transaction)
}
@ -363,7 +363,7 @@ public class StickerManager: NSObject {
}
private class func markSavedStickerPackAsInstalled(
stickerPack: StickerPack,
stickerPack: StickerPackRecord,
wasLocallyInitiated: Bool,
transaction: DBWriteTransaction,
) -> Promise<Void> {
@ -371,7 +371,7 @@ public class StickerManager: NSObject {
return .value(())
}
stickerPack.update(withIsInstalled: true, transaction: transaction)
stickerPack.updateWith(isInstalled: true, tx: transaction)
let promise = installStickerPackContents(stickerPack: stickerPack, transaction: transaction)
@ -386,7 +386,7 @@ public class StickerManager: NSObject {
}
private class func installStickerPackContents(
stickerPack: StickerPack,
stickerPack: StickerPackRecord,
transaction: DBReadTransaction,
onlyInstallCover: Bool = false,
) -> Promise<Void> {
@ -456,7 +456,7 @@ public class StickerManager: NSObject {
}
public class func installedStickers(
forStickerPack stickerPack: StickerPack,
forStickerPack stickerPack: StickerPackRecord,
verifyExists: Bool,
) -> [StickerInfo] {
return SSKEnvironment.shared.databaseStorageRef.read { transaction in
@ -469,12 +469,12 @@ public class StickerManager: NSObject {
}
public class func installedStickers(
forStickerPack stickerPack: StickerPack,
forStickerPack stickerPack: StickerPackRecord,
verifyExists: Bool,
transaction: DBReadTransaction,
) -> [StickerInfo] {
var result = [StickerInfo]()
for stickerInfo in stickerPack.stickerInfos {
for stickerInfo in stickerPack.stickerInfos() {
let uniqueId = InstalledSticker.uniqueId(for: stickerInfo)
guard let installedSticker = InstalledSticker.anyFetch(uniqueId: uniqueId, transaction: transaction) else {
continue
@ -723,11 +723,11 @@ public class StickerManager: NSObject {
}
private class func tryToDownloadAndInstallSticker(
stickerPack: StickerPack,
stickerPack: StickerPackRecord,
item: StickerPackItem,
transaction: DBReadTransaction,
) -> Promise<Bool> {
let stickerInfo: StickerInfo = item.stickerInfo(with: stickerPack)
let stickerInfo: StickerInfo = item.stickerInfoWith(stickerPack: stickerPack)
let emojiString = item.emojiString
guard !self.isStickerInstalled(stickerInfo: stickerInfo, transaction: transaction) else {
@ -1001,7 +1001,7 @@ public class StickerManager: NSObject {
}
}
public class func ensureDownloadsAsync(forStickerPack stickerPack: StickerPack) -> Promise<Void> {
public class func ensureDownloadsAsync(forStickerPack stickerPack: StickerPackRecord) -> Promise<Void> {
let (promise, future) = Promise<Void>.pending()
DispatchQueue.global().async {
SSKEnvironment.shared.databaseStorageRef.read { transaction in
@ -1018,7 +1018,7 @@ public class StickerManager: NSObject {
}
@discardableResult
private class func ensureDownloads(forStickerPack stickerPack: StickerPack, transaction: DBReadTransaction) -> Promise<Void> {
private class func ensureDownloads(forStickerPack stickerPack: StickerPackRecord, transaction: DBReadTransaction) -> Promise<Void> {
// TODO: As an optimization, we could flag packs as "complete" if we know all
// of their stickers are installed.
@ -1054,11 +1054,11 @@ public class StickerManager: NSObject {
}
}
private static func fetchOrphanedPacksAndStickers(tx: DBReadTransaction) -> ([StickerPack], [InstalledSticker]) {
var stickerPacks = [String: StickerPack]()
var packsToRemove = [StickerPack]()
private static func fetchOrphanedPacksAndStickers(tx: DBReadTransaction) -> ([StickerPackRecord], [InstalledSticker]) {
var stickerPacks = [String: StickerPackRecord]()
var packsToRemove = [StickerPackRecord]()
for stickerPack in StickerPack.anyFetchAll(transaction: tx) {
for stickerPack in StickerPackRecord.anyFetchAll(transaction: tx) {
if stickerPack.isInstalled || self.isDefaultStickerPack(packId: stickerPack.info.packId) {
stickerPacks[stickerPack.info.asKey] = stickerPack
} else {

View File

@ -1,509 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import GRDB
// NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py.
// Do not manually edit it, instead run `sds_codegen.sh`.
// MARK: - Record
public struct StickerPackRecord: SDSRecord {
public weak var delegate: SDSRecordDelegate?
public var tableMetadata: SDSTableMetadata {
StickerPackSerializer.table
}
public static var databaseTableName: String {
StickerPackSerializer.table.tableName
}
public var id: Int64?
// This defines all of the columns used in the table
// where this model (and any subclasses) are persisted.
public let recordType: SDSRecordType?
public let uniqueId: String
// Properties
public let author: String?
public let cover: Data
public let dateCreated: Double
public let info: Data
public let isInstalled: Bool
public let items: Data
public let title: String?
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
case id
case recordType
case uniqueId
case author
case cover
case dateCreated
case info
case isInstalled
case items
case title
}
public static func columnName(_ column: StickerPackRecord.CodingKeys, fullyQualified: Bool = false) -> String {
fullyQualified ? "\(databaseTableName).\(column.rawValue)" : column.rawValue
}
public func didInsert(with rowID: Int64, for column: String?) {
guard let delegate = delegate else {
owsFailDebug("Missing delegate.")
return
}
delegate.updateRowId(rowID)
}
}
// MARK: - Row Initializer
public extension StickerPackRecord {
static var databaseSelection: [SQLSelectable] {
CodingKeys.allCases
}
init(row: Row) {
id = row[0]
recordType = row[1].flatMap { SDSRecordType(rawValue: $0) }
uniqueId = row[2]
author = row[3]
cover = row[4]
dateCreated = row[5]
info = row[6]
isInstalled = row[7]
items = row[8]
title = row[9]
}
}
// MARK: - StringInterpolation
public extension String.StringInterpolation {
mutating func appendInterpolation(stickerPackColumn column: StickerPackRecord.CodingKeys) {
appendLiteral(StickerPackRecord.columnName(column))
}
mutating func appendInterpolation(stickerPackColumnFullyQualified column: StickerPackRecord.CodingKeys) {
appendLiteral(StickerPackRecord.columnName(column, fullyQualified: true))
}
}
// MARK: - Deserialization
extension StickerPack {
// This method defines how to deserialize a model, given a
// database row. The recordType column is used to determine
// the corresponding model class.
class func fromRecord(_ record: StickerPackRecord) throws -> StickerPack {
guard let recordId = record.id else { throw SDSError.missingRequiredField(fieldName: "id") }
guard let recordType = record.recordType else { throw SDSError.missingRequiredField(fieldName: "recordType") }
switch recordType {
case .stickerPack:
let uniqueId: String = record.uniqueId
let author: String? = record.author
let coverSerialized: Data = record.cover
let cover: StickerPackItem = try SDSDeserialization.unarchivedObject(ofClass: StickerPackItem.self, from: coverSerialized)
let dateCreatedInterval: Double = record.dateCreated
let dateCreated: Date = SDSDeserialization.requiredDoubleAsDate(dateCreatedInterval, name: "dateCreated")
let infoSerialized: Data = record.info
let info: StickerPackInfo = try SDSDeserialization.unarchivedObject(ofClass: StickerPackInfo.self, from: infoSerialized)
let isInstalled: Bool = record.isInstalled
let itemsSerialized: Data = record.items
let items: [StickerPackItem] = try SDSDeserialization.unarchivedArrayOfObjects(ofClass: StickerPackItem.self, from: itemsSerialized)
let title: String? = record.title
return StickerPack(grdbId: recordId,
uniqueId: uniqueId,
author: author,
cover: cover,
dateCreated: dateCreated,
info: info,
isInstalled: isInstalled,
items: items,
title: title)
default:
owsFailDebug("Unexpected record type: \(recordType)")
throw SDSError.invalidValue()
}
}
}
// MARK: - SDSModel
extension StickerPack: SDSModel {
public var serializer: SDSSerializer {
// Any subclass can be cast to it's superclass,
// so the order of this switch statement matters.
// We need to do a "depth first" search by type.
switch self {
default:
return StickerPackSerializer(model: self)
}
}
public func asRecord() -> SDSRecord {
serializer.asRecord()
}
public var sdsTableName: String {
StickerPackRecord.databaseTableName
}
public static var table: SDSTableMetadata {
StickerPackSerializer.table
}
}
// MARK: - DeepCopyable
extension StickerPack: DeepCopyable {
public func deepCopy() throws -> AnyObject {
guard let id = self.grdbId?.int64Value else {
throw OWSAssertionError("Model missing grdbId.")
}
// Any subclass can be cast to its superclass, so the order of these if
// statements matters. We need to do a "depth first" search by type.
do {
let modelToCopy = self
assert(type(of: modelToCopy) == StickerPack.self)
let uniqueId: String = modelToCopy.uniqueId
let author: String? = modelToCopy.author
let cover: StickerPackItem = try DeepCopies.deepCopy(modelToCopy.cover)
let dateCreated: Date = modelToCopy.dateCreated
let info: StickerPackInfo = try DeepCopies.deepCopy(modelToCopy.info)
let isInstalled: Bool = modelToCopy.isInstalled
let items: [StickerPackItem] = try DeepCopies.deepCopy(modelToCopy.items)
let title: String? = modelToCopy.title
return StickerPack(grdbId: id,
uniqueId: uniqueId,
author: author,
cover: cover,
dateCreated: dateCreated,
info: info,
isInstalled: isInstalled,
items: items,
title: title)
}
}
}
// MARK: - Table Metadata
extension StickerPackSerializer {
// This defines all of the columns used in the table
// where this model (and any subclasses) are persisted.
static var idColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "id", columnType: .primaryKey) }
static var recordTypeColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "recordType", columnType: .int64) }
static var uniqueIdColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, isUnique: true) }
// Properties
static var authorColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "author", columnType: .unicodeString, isOptional: true) }
static var coverColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "cover", columnType: .blob) }
static var dateCreatedColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "dateCreated", columnType: .double) }
static var infoColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "info", columnType: .blob) }
static var isInstalledColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "isInstalled", columnType: .int) }
static var itemsColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "items", columnType: .blob) }
static var titleColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "title", columnType: .unicodeString, isOptional: true) }
public static var table: SDSTableMetadata {
SDSTableMetadata(
tableName: "model_StickerPack",
columns: [
idColumn,
recordTypeColumn,
uniqueIdColumn,
authorColumn,
coverColumn,
dateCreatedColumn,
infoColumn,
isInstalledColumn,
itemsColumn,
titleColumn,
]
)
}
}
// MARK: - Save/Remove/Update
@objc
public extension StickerPack {
func anyInsert(transaction: DBWriteTransaction) {
sdsSave(saveMode: .insert, transaction: transaction)
}
// Avoid this method whenever feasible.
//
// If the record has previously been saved, this method does an overwriting
// update of the corresponding row, otherwise if it's a new record, this
// method inserts a new row.
//
// For performance, when possible, you should explicitly specify whether
// you are inserting or updating rather than calling this method.
func anyUpsert(transaction: DBWriteTransaction) {
let isInserting: Bool
if StickerPack.anyFetch(uniqueId: uniqueId, transaction: transaction) != nil {
isInserting = false
} else {
isInserting = true
}
sdsSave(saveMode: isInserting ? .insert : .update, transaction: transaction)
}
// This method is used by "updateWith..." methods.
//
// This model may be updated from many threads. We don't want to save
// our local copy (this instance) since it may be out of date. We also
// want to avoid re-saving a model that has been deleted. Therefore, we
// use "updateWith..." methods to:
//
// a) Update a property of this instance.
// b) If a copy of this model exists in the database, load an up-to-date copy,
// and update and save that copy.
// b) If a copy of this model _DOES NOT_ exist in the database, do _NOT_ save
// this local instance.
//
// After "updateWith...":
//
// a) Any copy of this model in the database will have been updated.
// b) The local property on this instance will always have been updated.
// c) Other properties on this instance may be out of date.
//
// All mutable properties of this class have been made read-only to
// prevent accidentally modifying them directly.
//
// This isn't a perfect arrangement, but in practice this will prevent
// data loss and will resolve all known issues.
func anyUpdate(transaction: DBWriteTransaction, block: (StickerPack) -> Void) {
block(self)
// If it's not saved, we don't expect to find it in the database, and we
// won't save any changes we make back into the database.
guard shouldBeSaved else {
return
}
guard let dbCopy = type(of: self).anyFetch(uniqueId: uniqueId, transaction: transaction) else {
return
}
// Don't apply the block twice to the same instance.
// It's at least unnecessary and actually wrong for some blocks.
// e.g. `block: { $0 in $0.someField++ }`
if dbCopy !== self {
block(dbCopy)
}
dbCopy.sdsSave(saveMode: .update, transaction: transaction)
}
// This method is an alternative to `anyUpdate(transaction:block:)` methods.
//
// We should generally use `anyUpdate` to ensure we're not unintentionally
// clobbering other columns in the database when another concurrent update
// has occurred.
//
// There are cases when this doesn't make sense, e.g. when we know we've
// just loaded the model in the same transaction. In those cases it is
// safe and faster to do a "overwriting" update
func anyOverwritingUpdate(transaction: DBWriteTransaction) {
sdsSave(saveMode: .update, transaction: transaction)
}
func anyRemove(transaction: DBWriteTransaction) {
sdsRemove(transaction: transaction)
}
}
// MARK: - StickerPackCursor
@objc
public class StickerPackCursor: NSObject, SDSCursor {
private let transaction: DBReadTransaction
private let cursor: RecordCursor<StickerPackRecord>
init(transaction: DBReadTransaction, cursor: RecordCursor<StickerPackRecord>) {
self.transaction = transaction
self.cursor = cursor
}
public func next() throws -> StickerPack? {
guard let record = try cursor.next() else {
return nil
}
return try StickerPack.fromRecord(record)
}
public func all() throws -> [StickerPack] {
var result = [StickerPack]()
while true {
guard let model = try next() else {
break
}
result.append(model)
}
return result
}
}
// MARK: - Obj-C Fetch
@objc
public extension StickerPack {
@nonobjc
class func grdbFetchCursor(transaction: DBReadTransaction) -> StickerPackCursor {
let database = transaction.database
return failIfThrows {
let cursor = try StickerPackRecord.fetchCursor(database)
return StickerPackCursor(transaction: transaction, cursor: cursor)
}
}
// Fetches a single model by "unique id".
class func anyFetch(uniqueId: String,
transaction: DBReadTransaction) -> StickerPack? {
assert(!uniqueId.isEmpty)
let sql = "SELECT * FROM \(StickerPackRecord.databaseTableName) WHERE \(stickerPackColumn: .uniqueId) = ?"
return grdbFetchOne(sql: sql, arguments: [uniqueId], transaction: transaction)
}
// Traverses all records.
// Records are not visited in any particular order.
class func anyEnumerate(
transaction: DBReadTransaction,
block: (StickerPack, UnsafeMutablePointer<ObjCBool>) -> Void
) {
anyEnumerate(transaction: transaction, batched: false, block: block)
}
// Traverses all records.
// Records are not visited in any particular order.
class func anyEnumerate(
transaction: DBReadTransaction,
batched: Bool = false,
block: (StickerPack, UnsafeMutablePointer<ObjCBool>) -> Void
) {
let batchSize = batched ? Batching.kDefaultBatchSize : 0
anyEnumerate(transaction: transaction, batchSize: batchSize, block: block)
}
// Traverses all records.
// Records are not visited in any particular order.
//
// If batchSize > 0, the enumeration is performed in autoreleased batches.
class func anyEnumerate(
transaction: DBReadTransaction,
batchSize: UInt,
block: (StickerPack, UnsafeMutablePointer<ObjCBool>) -> Void
) {
let cursor = StickerPack.grdbFetchCursor(transaction: transaction)
Batching.loop(batchSize: batchSize,
loopBlock: { stop in
do {
guard let value = try cursor.next() else {
stop.pointee = true
return
}
block(value, stop)
} catch let error {
owsFailDebug("Couldn't fetch model: \(error)")
}
})
}
// Does not order the results.
class func anyFetchAll(transaction: DBReadTransaction) -> [StickerPack] {
var result = [StickerPack]()
anyEnumerate(transaction: transaction) { (model, _) in
result.append(model)
}
return result
}
class func anyCount(transaction: DBReadTransaction) -> UInt {
return StickerPackRecord.ows_fetchCount(transaction.database)
}
}
// MARK: - Swift Fetch
public extension StickerPack {
class func grdbFetchCursor(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: DBReadTransaction) -> StickerPackCursor {
return failIfThrows {
let sqlRequest = SQLRequest<Void>(sql: sql, arguments: arguments, cached: true)
let cursor = try StickerPackRecord.fetchCursor(transaction.database, sqlRequest)
return StickerPackCursor(transaction: transaction, cursor: cursor)
}
}
class func grdbFetchOne(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: DBReadTransaction) -> StickerPack? {
assert(!sql.isEmpty)
do {
let sqlRequest = SQLRequest<Void>(sql: sql, arguments: arguments, cached: true)
guard let record = try StickerPackRecord.fetchOne(transaction.database, sqlRequest) else {
return nil
}
return try StickerPack.fromRecord(record)
} catch {
owsFailDebug("error: \(error)")
return nil
}
}
}
// MARK: - SDSSerializer
// The SDSSerializer protocol specifies how to insert and update the
// row that corresponds to this model.
class StickerPackSerializer: SDSSerializer {
private let model: StickerPack
public init(model: StickerPack) {
self.model = model
}
// MARK: - Record
func asRecord() -> SDSRecord {
let id: Int64? = model.grdbId?.int64Value
let recordType: SDSRecordType = .stickerPack
let uniqueId: String = model.uniqueId
// Properties
let author: String? = model.author
let cover: Data = requiredArchive(model.cover)
let dateCreated: Double = archiveDate(model.dateCreated)
let info: Data = requiredArchive(model.info)
let isInstalled: Bool = model.isInstalled
let items: Data = requiredArchive(model.items)
let title: String? = model.title
return StickerPackRecord(delegate: model, id: id, recordType: recordType, uniqueId: uniqueId, author: author, cover: cover, dateCreated: dateCreated, info: info, isInstalled: isInstalled, items: items, title: title)
}
}

View File

@ -1,90 +0,0 @@
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
@import Foundation;
#import <SignalServiceKit/BaseModel.h>
#import <SignalServiceKit/StickerInfo.h>
NS_ASSUME_NONNULL_BEGIN
@class DBWriteTransaction;
@class StickerPack;
@interface StickerPackItem : NSObject <NSSecureCoding, NSCopying>
@property (nonatomic, readonly) UInt32 stickerId;
@property (nonatomic, readonly) NSString *emojiString;
@property (nonatomic, readonly, nullable) NSString *contentType;
- (instancetype)initWithStickerId:(UInt32)stickerId
emojiString:(NSString *)emojiString
contentType:(nullable NSString *)contentType;
- (StickerInfo *)stickerInfoWithStickerPack:(StickerPack *)stickerPack;
@end
#pragma mark -
@interface StickerPack : BaseModel <NSCopying>
@property (nonatomic, readonly) StickerPackInfo *info;
@property (nonatomic, readonly, nullable) NSString *title;
@property (nonatomic, readonly, nullable) NSString *author;
@property (nonatomic, readonly) StickerPackItem *cover;
@property (nonatomic, readonly) NSArray<StickerPackItem *> *items;
// Convenience accessors.
@property (nonatomic, readonly) NSData *packId;
@property (nonatomic, readonly) NSData *packKey;
@property (nonatomic, readonly) StickerInfo *coverInfo;
@property (nonatomic, readonly) NSArray<StickerInfo *> *stickerInfos;
@property (nonatomic, readonly) NSDate *dateCreated;
@property (nonatomic, readonly) BOOL isInstalled;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithGrdbId:(int64_t)grdbId uniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithInfo:(StickerPackInfo *)info
title:(nullable NSString *)title
author:(nullable NSString *)author
cover:(StickerPackItem *)cover
stickers:(NSArray<StickerPackItem *> *)items NS_DESIGNATED_INITIALIZER;
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
author:(nullable NSString *)author
cover:(StickerPackItem *)cover
dateCreated:(NSDate *)dateCreated
info:(StickerPackInfo *)info
isInstalled:(BOOL)isInstalled
items:(NSArray<StickerPackItem *> *)items
title:(nullable NSString *)title
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:author:cover:dateCreated:info:isInstalled:items:title:));
// clang-format on
// --- CODE GENERATION MARKER
+ (NSString *)uniqueIdForStickerPackInfo:(StickerPackInfo *)info;
- (void)updateWithIsInstalled:(BOOL)isInstalled transaction:(DBWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,276 +0,0 @@
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "StickerPack.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@implementation StickerPackItem
- (instancetype)initWithStickerId:(UInt32)stickerId
emojiString:(NSString *)emojiString
contentType:(nullable NSString *)contentType
{
self = [super init];
if (!self) {
return self;
}
_stickerId = stickerId;
_emojiString = emojiString;
if (contentType.length > 0) {
_contentType = contentType;
}
return self;
}
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
NSString *contentType = self.contentType;
if (contentType != nil) {
[coder encodeObject:contentType forKey:@"contentType"];
}
NSString *emojiString = self.emojiString;
if (emojiString != nil) {
[coder encodeObject:emojiString forKey:@"emojiString"];
}
[coder encodeObject:[self valueForKey:@"stickerId"] forKey:@"stickerId"];
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (!self) {
return self;
}
self->_contentType = [coder decodeObjectOfClass:[NSString class] forKey:@"contentType"];
self->_emojiString = [coder decodeObjectOfClass:[NSString class] forKey:@"emojiString"];
self->_stickerId = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class] forKey:@"stickerId"] unsignedIntValue];
return self;
}
- (NSUInteger)hash
{
NSUInteger result = 0;
result ^= self.contentType.hash;
result ^= self.emojiString.hash;
result ^= self.stickerId;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![other isMemberOfClass:self.class]) {
return NO;
}
StickerPackItem *typedOther = (StickerPackItem *)other;
if (![NSObject isObject:self.contentType equalToObject:typedOther.contentType]) {
return NO;
}
if (![NSObject isObject:self.emojiString equalToObject:typedOther.emojiString]) {
return NO;
}
if (self.stickerId != typedOther.stickerId) {
return NO;
}
return YES;
}
- (id)copyWithZone:(nullable NSZone *)zone
{
StickerPackItem *result = [[[self class] allocWithZone:zone] init];
result->_contentType = self.contentType;
result->_emojiString = self.emojiString;
result->_stickerId = self.stickerId;
return result;
}
- (StickerInfo *)stickerInfoWithStickerPack:(StickerPack *)stickerPack
{
return [[StickerInfo alloc] initWithPackId:stickerPack.packId packKey:stickerPack.packKey stickerId:self.stickerId];
}
@end
#pragma mark -
@interface StickerPack ()
@property (nonatomic) BOOL isInstalled;
@end
#pragma mark -
@implementation StickerPack
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.author.hash;
result ^= self.cover.hash;
result ^= self.dateCreated.hash;
result ^= self.info.hash;
result ^= self.isInstalled;
result ^= self.items.hash;
result ^= self.title.hash;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
StickerPack *typedOther = (StickerPack *)other;
if (![NSObject isObject:self.author equalToObject:typedOther.author]) {
return NO;
}
if (![NSObject isObject:self.cover equalToObject:typedOther.cover]) {
return NO;
}
if (![NSObject isObject:self.dateCreated equalToObject:typedOther.dateCreated]) {
return NO;
}
if (![NSObject isObject:self.info equalToObject:typedOther.info]) {
return NO;
}
if (self.isInstalled != typedOther.isInstalled) {
return NO;
}
if (![NSObject isObject:self.items equalToObject:typedOther.items]) {
return NO;
}
if (![NSObject isObject:self.title equalToObject:typedOther.title]) {
return NO;
}
return YES;
}
- (id)copyWithZone:(nullable NSZone *)zone
{
StickerPack *result = [self copyAndAssignIdsWithZone:zone];
result->_author = self.author;
result->_cover = self.cover;
result->_dateCreated = self.dateCreated;
result->_info = self.info;
result->_isInstalled = self.isInstalled;
result->_items = self.items;
result->_title = self.title;
return result;
}
- (instancetype)initWithInfo:(StickerPackInfo *)info
title:(nullable NSString *)title
author:(nullable NSString *)author
cover:(StickerPackItem *)cover
stickers:(NSArray<StickerPackItem *> *)items
{
OWSAssertDebug(info.packId.length > 0);
OWSAssertDebug(info.packKey.length > 0);
// Title and empty might be nil or empty.
OWSAssertDebug(cover);
OWSAssertDebug(items.count > 0);
self = [super initWithUniqueId:[StickerPack uniqueIdForStickerPackInfo:info]];
if (!self) {
return self;
}
_info = info;
_title = title;
_author = author;
_cover = cover;
_items = items;
_dateCreated = [NSDate new];
return self;
}
- (NSData *)packId
{
return self.info.packId;
}
- (NSData *)packKey
{
return self.info.packKey;
}
- (StickerInfo *)coverInfo
{
return [[StickerInfo alloc] initWithPackId:self.packId packKey:self.packKey stickerId:self.cover.stickerId];
}
- (NSArray<StickerInfo *> *)stickerInfos
{
NSMutableArray<StickerInfo *> *stickerInfos = [NSMutableArray new];
for (StickerPackItem *item in self.items) {
[stickerInfos addObject:[item stickerInfoWithStickerPack:self]];
}
return stickerInfos;
}
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
author:(nullable NSString *)author
cover:(StickerPackItem *)cover
dateCreated:(NSDate *)dateCreated
info:(StickerPackInfo *)info
isInstalled:(BOOL)isInstalled
items:(NSArray<StickerPackItem *> *)items
title:(nullable NSString *)title
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId];
if (!self) {
return self;
}
_author = author;
_cover = cover;
_dateCreated = dateCreated;
_info = info;
_isInstalled = isInstalled;
_items = items;
_title = title;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
+ (NSString *)uniqueIdForStickerPackInfo:(StickerPackInfo *)info
{
return info.asKey;
}
- (void)updateWithIsInstalled:(BOOL)isInstalled transaction:(DBWriteTransaction *)transaction
{
[self anyUpdateWithTransaction:transaction block:^(StickerPack *instance) { instance.isInstalled = isInstalled; }];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,65 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
@objc(StickerPackItem)
public final class StickerPackItem: NSObject, NSSecureCoding, NSCopying {
public let stickerId: UInt32
public let emojiString: String
public let contentType: String?
init(
stickerId: UInt32,
emojiString: String,
contentType: String?,
) {
self.stickerId = stickerId
self.emojiString = emojiString
self.contentType = contentType?.nilIfEmpty
}
public static var supportsSecureCoding: Bool { true }
public func encode(with coder: NSCoder) {
if let contentType {
coder.encode(contentType, forKey: "contentType")
}
coder.encode(self.emojiString, forKey: "emojiString")
coder.encode(NSNumber(value: self.stickerId), forKey: "stickerId")
}
public init?(coder: NSCoder) {
self.contentType = (coder.decodeObject(of: NSString.self, forKey: "contentType") as String?)?.nilIfEmpty
self.emojiString = coder.decodeObject(of: NSString.self, forKey: "emojiString") as String? ?? ""
self.stickerId = coder.decodeObject(of: NSNumber.self, forKey: "stickerId")?.uint32Value ?? 0
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(self.contentType)
hasher.combine(self.emojiString)
hasher.combine(self.stickerId)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard self.contentType == object.contentType else { return false }
guard self.emojiString == object.emojiString else { return false }
guard self.stickerId == object.stickerId else { return false }
return true
}
public func copy(with zone: NSZone? = nil) -> Any {
return self
}
func stickerInfoWith(stickerPack: StickerPackRecord) -> StickerInfo {
let packId = stickerPack.info.packId
let packKey = stickerPack.info.packKey
return StickerInfo(packId: packId, packKey: packKey, stickerId: self.stickerId)
}
}

View File

@ -0,0 +1,163 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import GRDB
public final class StickerPackRecord: SDSCodableModel, Decodable, Equatable, NSCopying {
public static let databaseTableName: String = "model_StickerPack"
public static let recordType: UInt = SDSRecordType.stickerPack.rawValue
public var id: Int64?
public let uniqueId: String
public let info: StickerPackInfo
public let title: String?
public let author: String?
public let cover: StickerPackItem
public let items: [StickerPackItem]
public let dateCreated: Date
public private(set) var isInstalled: Bool
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
case id
case recordType
case uniqueId
case author
case cover
case dateCreated
case info
case isInstalled
case items
case title
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(Int64.self, forKey: .id)
self.uniqueId = try container.decode(String.self, forKey: .uniqueId)
let infoData = try container.decode(Data.self, forKey: .info)
self.info = try LegacySDSSerializer().deserializeLegacySDSData(infoData, ofClass: StickerPackInfo.self)
self.title = try container.decodeIfPresent(String.self, forKey: .title)
self.author = try container.decodeIfPresent(String.self, forKey: .author)
let coverData = try container.decode(Data.self, forKey: .cover)
self.cover = try LegacySDSSerializer().deserializeLegacySDSData(coverData, ofClass: StickerPackItem.self)
let itemsData = try container.decode(Data.self, forKey: .items)
self.items = try LegacySDSSerializer().deserializeLegacyArchivedArray(itemsData, ofClass: StickerPackItem.self)
self.dateCreated = Date(timeIntervalSince1970: try container.decode(TimeInterval.self, forKey: .dateCreated))
self.isInstalled = try container.decode(Bool.self, forKey: .isInstalled)
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.id, forKey: .id)
try container.encode(self.uniqueId, forKey: .uniqueId)
try container.encode(Self.recordType, forKey: .recordType)
try container.encode(LegacySDSSerializer().serializeAsLegacySDSData(self.info), forKey: .info)
try container.encodeIfPresent(self.title, forKey: .title)
try container.encodeIfPresent(self.author, forKey: .author)
try container.encode(LegacySDSSerializer().serializeAsLegacySDSData(self.cover), forKey: .cover)
try container.encode(LegacySDSSerializer().serializeAsLegacySDSData(self.items as [NSSecureCoding] as NSArray), forKey: .items)
try container.encode(self.dateCreated.timeIntervalSince1970, forKey: .dateCreated)
try container.encode(self.isInstalled, forKey: .isInstalled)
}
public static func ==(lhs: StickerPackRecord, rhs: StickerPackRecord) -> Bool {
guard lhs.author == rhs.author else { return false }
guard lhs.cover == rhs.cover else { return false }
guard lhs.dateCreated == rhs.dateCreated else { return false }
guard lhs.info == rhs.info else { return false }
guard lhs.isInstalled == rhs.isInstalled else { return false }
guard lhs.items == rhs.items else { return false }
guard lhs.title == rhs.title else { return false }
return true
}
convenience init(
info: StickerPackInfo,
title: String?,
author: String?,
cover: StickerPackItem,
items: [StickerPackItem],
) {
owsAssertDebug(!info.packId.isEmpty)
owsAssertDebug(!info.packKey.isEmpty)
owsAssertDebug(!items.isEmpty)
self.init(
id: nil,
uniqueId: Self.uniqueId(forStickerPackInfo: info),
info: info,
title: title,
author: author,
cover: cover,
items: items,
dateCreated: Date(),
isInstalled: false,
)
}
private init(
id: Int64?,
uniqueId: String,
info: StickerPackInfo,
title: String?,
author: String?,
cover: StickerPackItem,
items: [StickerPackItem],
dateCreated: Date,
isInstalled: Bool,
) {
self.id = id
self.uniqueId = uniqueId
self.info = info
self.title = title
self.author = author
self.cover = cover
self.items = items
self.dateCreated = dateCreated
self.isInstalled = isInstalled
}
public func copy(with zone: NSZone? = nil) -> Any {
return Self(
id: self.id,
uniqueId: self.uniqueId,
info: self.info,
title: self.title,
author: self.author,
cover: self.cover,
items: self.items,
dateCreated: self.dateCreated,
isInstalled: self.isInstalled,
)
}
public var packId: Data {
// This was the effective behavior in Swift via Obj-C.
return self.info.packId ?? Data()
}
public var packKey: Data {
// This was the effective behavior in Swift via Obj-C.
return self.info.packKey ?? Data()
}
public var coverInfo: StickerInfo {
let packId = self.info.packId
let packKey = self.info.packKey
return StickerInfo(packId: packId, packKey: packKey, stickerId: self.cover.stickerId)
}
public func stickerInfos() -> [StickerInfo] {
return self.items.map({ $0.stickerInfoWith(stickerPack: self) })
}
static func uniqueId(forStickerPackInfo stickerPackInfo: StickerPackInfo) -> String {
return stickerPackInfo.asKey
}
func updateWith(isInstalled: Bool, tx: DBWriteTransaction) {
anyUpdate(transaction: tx, block: { $0.isInstalled = isInstalled })
}
}

View File

@ -34,7 +34,6 @@ FOUNDATION_EXPORT const unsigned char SignalServiceKitVersionString[];
#import <SignalServiceKit/OWSVerificationStateChangeMessage.h>
#import <SignalServiceKit/SSKAccessors+SDS.h>
#import <SignalServiceKit/StickerInfo.h>
#import <SignalServiceKit/StickerPack.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSErrorMessage.h>

View File

@ -527,7 +527,7 @@ public enum DatabaseRecovery {
PendingViewedReceiptRecord.databaseTableName,
// Can be recovered in other ways, after recovery is done.
ProfileBadge.databaseTableName,
StickerPack.table.tableName,
StickerPackRecord.databaseTableName,
HiddenRecipient.databaseTableName,
// Not essential.
StoryContextAssociatedData.databaseTableName,

View File

@ -40,4 +40,18 @@ struct SDSCodableModelLegacySerializer: SDSSerializer {
}
return result
}
func deserializeLegacyArchivedArray<T: NSObject & NSSecureCoding>(_ encodedValue: Data, ofClass cls: T.Type) throws -> [T] {
let result: [T]?
do {
result = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: cls, from: encodedValue)
} catch {
Logger.warn("couldn't decode legacy data: \(error)")
throw SDSError.invalidValue()
}
guard let result else {
throw SDSError.invalidValue()
}
return result
}
}

View File

@ -102,11 +102,11 @@ public class StickerPackCollectionView: UICollectionView {
// MARK: Modes
public func showInstalledPack(stickerPack: StickerPack) {
public func showInstalledPack(stickerPack: StickerPackRecord) {
stickerPackDataSource = InstalledStickerPackDataSource(stickerPackInfo: stickerPack.info)
}
public func showUninstalledPack(stickerPack: StickerPack) {
public func showUninstalledPack(stickerPack: StickerPackRecord) {
stickerPackDataSource = TransientStickerPackDataSource(
stickerPackInfo: stickerPack.info,
shouldDownloadAllStickers: true,
@ -117,7 +117,7 @@ public class StickerPackCollectionView: UICollectionView {
stickerPackDataSource = RecentStickerPackDataSource()
}
public func showInstalledPackOrRecents(stickerPack: StickerPack?) {
public func showInstalledPackOrRecents(stickerPack: StickerPackRecord?) {
if let stickerPack {
showInstalledPack(stickerPack: stickerPack)
} else {
@ -225,7 +225,7 @@ public class StickerPackCollectionView: UICollectionView {
let installedStickerInfos = stickerPackDataSource.installedStickerInfos
if stickerPackDataSource is TransientStickerPackDataSource {
guard let allStickerInfos = stickerPackDataSource.getStickerPack()?.stickerInfos else {
guard let allStickerInfos = stickerPackDataSource.getStickerPack()?.stickerInfos() else {
stickerInfos = []
owsAssertDebug(installedStickerInfos.isEmpty)
return

View File

@ -22,7 +22,7 @@ public protocol StickerPackDataSource: AnyObject {
var title: String? { get }
var author: String? { get }
func getStickerPack() -> StickerPack?
func getStickerPack() -> StickerPackRecord?
var installedCoverInfo: StickerInfo? { get }
var installedStickerInfos: [StickerInfo] { get }
@ -118,7 +118,7 @@ public class InstalledStickerPackDataSource: BaseStickerPackDataSource {
private let stickerPackInfo: StickerPackInfo
fileprivate var stickerPack: StickerPack? {
fileprivate var stickerPack: StickerPackRecord? {
didSet {
AssertIsOnMainThread()
@ -195,7 +195,7 @@ public class InstalledStickerPackDataSource: BaseStickerPackDataSource {
}
private static func fetchInstalledState(for stickerPackInfo: StickerPackInfo, readTx: DBReadTransaction) -> (
stickerPack: StickerPack?,
stickerPack: StickerPackRecord?,
installedCoverInfo: StickerInfo?,
installedStickers: [StickerInfo],
) {
@ -273,7 +273,7 @@ extension InstalledStickerPackDataSource: StickerPackDataSource {
return stickerPack?.author
}
public func getStickerPack() -> StickerPack? {
public func getStickerPack() -> StickerPackRecord? {
return stickerPack
}
@ -313,7 +313,7 @@ public class TransientStickerPackDataSource: BaseStickerPackDataSource {
// If false, only download manifest and cover.
private let shouldDownloadAllStickers: Bool
fileprivate var stickerPack: StickerPack? {
fileprivate var stickerPack: StickerPackRecord? {
didSet {
AssertIsOnMainThread()
@ -394,7 +394,7 @@ public class TransientStickerPackDataSource: BaseStickerPackDataSource {
if shouldDownloadAllStickers {
var downloadedStickerInfos = [StickerInfo]()
for stickerInfo in stickerPack.stickerInfos {
for stickerInfo in stickerPack.stickerInfos() {
if ensureStickerDownload(stickerPack: stickerPack, stickerInfo: stickerInfo) {
downloadedStickerInfos.append(stickerInfo)
}
@ -448,7 +448,7 @@ public class TransientStickerPackDataSource: BaseStickerPackDataSource {
// Returns true if sticker is already downloaded.
// If not, kicks off the download.
private func ensureStickerDownload(stickerPack: StickerPack, stickerInfo: StickerInfo) -> Bool {
private func ensureStickerDownload(stickerPack: StickerPackRecord, stickerInfo: StickerInfo) -> Bool {
AssertIsOnMainThread()
guard let stickerPackItem = stickerPack.stickerPackItem(forStickerInfo: stickerInfo) else {
@ -552,7 +552,7 @@ extension TransientStickerPackDataSource: StickerPackDataSource {
return stickerPack?.author
}
public func getStickerPack() -> StickerPack? {
public func getStickerPack() -> StickerPackRecord? {
AssertIsOnMainThread()
if let stickerPack = installedDataSource.getStickerPack() {
@ -657,7 +657,7 @@ extension RecentStickerPackDataSource: StickerPackDataSource {
return nil
}
public func getStickerPack() -> StickerPack? {
public func getStickerPack() -> StickerPackRecord? {
owsFailDebug("This method should never be called.")
return nil
}
@ -684,7 +684,7 @@ extension RecentStickerPackDataSource: StickerPackDataSource {
// MARK: -
extension StickerPack {
extension StickerPackRecord {
func stickerPackItem(forStickerInfo stickerInfo: StickerInfo) -> StickerPackItem? {
if cover.stickerId == stickerInfo.stickerId {
return cover

View File

@ -377,9 +377,9 @@ private class StickerPickerPageView: UIView {
private let storyStickerConfiguration: StoryStickerConfiguration
private var stickerPacks = [StickerPack]()
private var stickerPacks = [StickerPackRecord]()
private var selectedStickerPack: StickerPack? {
private var selectedStickerPack: StickerPackRecord? {
didSet {
selectedPackChanged(oldSelectedPack: oldValue)
}
@ -534,7 +534,7 @@ private class StickerPickerPageView: UIView {
updateSelectedStickerPack(nil)
}
private func updateSelectedStickerPack(_ stickerPack: StickerPack?, scrollToSelected: Bool = false) {
private func updateSelectedStickerPack(_ stickerPack: StickerPackRecord?, scrollToSelected: Bool = false) {
selectedStickerPack = stickerPack
delegate?.updateSelections(scrollToSelectedItem: scrollToSelected)
}
@ -575,7 +575,7 @@ private class StickerPickerPageView: UIView {
return scrollView
}()
private var nextPageStickerPack: StickerPack? {
private var nextPageStickerPack: StickerPackRecord? {
// If we don't have a pack defined, the first pack is always up next
guard let stickerPack = selectedStickerPack else { return stickerPacks.first }
@ -586,7 +586,7 @@ private class StickerPickerPageView: UIView {
return stickerPacks[index + 1]
}
private var previousPageStickerPack: StickerPack? {
private var previousPageStickerPack: StickerPackRecord? {
// If we don't have a pack defined, the last pack is always previous
guard let stickerPack = selectedStickerPack else { return stickerPacks.last }
@ -692,7 +692,7 @@ private class StickerPickerPageView: UIView {
pendingPageChangeUpdates = nil
}
private func selectedPackChanged(oldSelectedPack: StickerPack?) {
private func selectedPackChanged(oldSelectedPack: StickerPackRecord?) {
AssertIsOnMainThread()
// We're paging backwards!