From e69b7d481bb2dc9016d4b14802f2abd7e987e8a4 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 17 Apr 2019 16:02:32 -0400 Subject: [PATCH] Sketch out installed sticker classes. --- .../sds_config/sds_record_type_map.json | 2 + .../Attachments/OWSThumbnailService.swift | 2 +- .../Stickers/InstalledSticker+SDS.swift | 249 ++++++++++++++++++ .../src/Messages/Stickers/InstalledSticker.h | 40 +++ .../src/Messages/Stickers/InstalledSticker.m | 66 +++++ .../Stickers/InstalledStickerPack+SDS.swift | 243 +++++++++++++++++ .../Messages/Stickers/InstalledStickerPack.h | 36 +++ .../Messages/Stickers/InstalledStickerPack.m | 63 +++++ .../Messages/Stickers/InstalledStickers.swift | 113 ++++++++ .../MessageSticker.swift | 0 SignalServiceKit/src/SignalServiceKit.h | 10 +- .../Storage/Database/SDSKeyValueStore.swift | 4 - .../src/Storage/Database/SDSRecordType.swift | 2 + 13 files changed, 821 insertions(+), 9 deletions(-) create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledSticker+SDS.swift create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledSticker.h create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledSticker.m create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledStickerPack+SDS.swift create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.h create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.m create mode 100644 SignalServiceKit/src/Messages/Stickers/InstalledStickers.swift rename SignalServiceKit/src/Messages/{Interactions => Stickers}/MessageSticker.swift (100%) diff --git a/Scripts/sds_codegen/sds_config/sds_record_type_map.json b/Scripts/sds_codegen/sds_config/sds_record_type_map.json index e581bbe608..fe6d5ae095 100644 --- a/Scripts/sds_codegen/sds_config/sds_record_type_map.json +++ b/Scripts/sds_codegen/sds_config/sds_record_type_map.json @@ -1,5 +1,7 @@ { "#comment": "NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.", + "InstalledSticker": 28, + "InstalledStickerPack": 27, "OWSAddToContactsOfferMessage": 23, "OWSAddToProfileWhitelistOfferMessage": 7, "OWSContactOffersInteraction": 21, diff --git a/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift b/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift index d65a502d53..1f7f1b7c5c 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift +++ b/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift @@ -168,7 +168,7 @@ private struct OWSThumbnailRequest { throw OWSThumbnailError.failure(description: "Could not convert thumbnail to JPEG.") } do { - try thumbnailData.write(to: URL(fileURLWithPath: thumbnailPath), options: .atomicWrite) + try thumbnailData.write(to: URL(fileURLWithPath: thumbnailPath), options: .atomic) } catch let error as NSError { throw OWSThumbnailError.externalError(description: "File write failed: \(thumbnailPath), \(error)", underlyingError: error) } diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledSticker+SDS.swift b/SignalServiceKit/src/Messages/Stickers/InstalledSticker+SDS.swift new file mode 100644 index 0000000000..667da6fe84 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledSticker+SDS.swift @@ -0,0 +1,249 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation +import GRDBCipher +import SignalCoreKit + +// NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py. +// Do not manually edit it, instead run `sds_codegen.sh`. + +// MARK: - SDSSerializable + +extension InstalledSticker: SDSSerializable { + 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 InstalledStickerSerializer(model: self) + } + } +} + +// MARK: - Table Metadata + +extension InstalledStickerSerializer { + + // This defines all of the columns used in the table + // where this model (and any subclasses) are persisted. + static let recordTypeColumn = SDSColumnMetadata(columnName: "recordType", columnType: .int, columnIndex: 0) + static let uniqueIdColumn = SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, columnIndex: 1) + // Base class properties + static let packIdColumn = SDSColumnMetadata(columnName: "packId", columnType: .blob, columnIndex: 2) + static let packKeyColumn = SDSColumnMetadata(columnName: "packKey", columnType: .blob, columnIndex: 3) + static let stickerIdColumn = SDSColumnMetadata(columnName: "stickerId", columnType: .int, columnIndex: 4) + + // TODO: We should decide on a naming convention for + // tables that store models. + public static let table = SDSTableMetadata(tableName: "model_InstalledSticker", columns: [ + recordTypeColumn, + uniqueIdColumn, + packIdColumn, + packKeyColumn, + stickerIdColumn + ]) + +} + +// MARK: - Deserialization + +extension InstalledStickerSerializer { + // 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 sdsDeserialize(statement: SelectStatement) throws -> InstalledSticker { + + if OWSIsDebugBuild() { + guard statement.columnNames == table.selectColumnNames else { + owsFailDebug("Unexpected columns: \(statement.columnNames) != \(table.selectColumnNames)") + throw SDSError.invalidResult + } + } + + // SDSDeserializer is used to convert column values into Swift values. + let deserializer = SDSDeserializer(sqliteStatement: statement.sqliteStatement) + let recordTypeValue = try deserializer.int(at: 0) + guard let recordType = SDSRecordType(rawValue: UInt(recordTypeValue)) else { + owsFailDebug("Invalid recordType: \(recordTypeValue)") + throw SDSError.invalidResult + } + switch recordType { + case .installedSticker: + + let uniqueId = try deserializer.string(at: uniqueIdColumn.columnIndex) + let packId = try deserializer.blob(at: packIdColumn.columnIndex) + let packKey = try deserializer.blob(at: packKeyColumn.columnIndex) + let stickerId = UInt32(try deserializer.int64(at: stickerIdColumn.columnIndex)) + + return InstalledSticker(uniqueId: uniqueId, + packId: packId, + packKey: packKey, + stickerId: stickerId) + + default: + owsFail("Invalid record type \(recordType)") + } + } +} + +// MARK: - Save/Remove/Update + +@objc +extension InstalledSticker { + @objc + public func anySave(transaction: SDSAnyWriteTransaction) { + switch transaction.writeTransaction { + case .yapWrite(let ydbTransaction): + save(with: ydbTransaction) + case .grdbWrite(let grdbTransaction): + SDSSerialization.save(entity: self, transaction: grdbTransaction) + } + } + + @objc + public func anyRemove(transaction: SDSAnyWriteTransaction) { + switch transaction.writeTransaction { + case .yapWrite(let ydbTransaction): + remove(with: ydbTransaction) + case .grdbWrite(let grdbTransaction): + SDSSerialization.delete(entity: self, transaction: grdbTransaction) + } + } +} + +// MARK: - InstalledStickerCursor + +@objc +public class InstalledStickerCursor: NSObject { + private let cursor: SDSCursor + + init(cursor: SDSCursor) { + self.cursor = cursor + } + + // TODO: Revisit error handling in this class. + public func next() throws -> InstalledSticker? { + return try cursor.next() + } + + public func all() throws -> [InstalledSticker] { + return try cursor.all() + } +} + +// MARK: - Obj-C Fetch + +// TODO: We may eventually want to define some combination of: +// +// * fetchCursor, fetchOne, fetchAll, etc. (ala GRDB) +// * Optional "where clause" parameters for filtering. +// * Async flavors with completions. +// +// TODO: I've defined flavors that take a read transaction. +// Or we might take a "connection" if we end up having that class. +@objc +extension InstalledSticker { + public class func grdbFetchCursor(transaction: GRDBReadTransaction) -> InstalledStickerCursor { + return InstalledStickerCursor(cursor: SDSSerialization.fetchCursor(tableMetadata: InstalledStickerSerializer.table, + transaction: transaction, + deserialize: InstalledStickerSerializer.sdsDeserialize)) + } +} + +// MARK: - Swift Fetch + +extension InstalledSticker { + public class func grdbFetchCursor(sql: String, + arguments: [DatabaseValueConvertible]?, + transaction: GRDBReadTransaction) -> InstalledStickerCursor { + var statementArguments: StatementArguments? + if let arguments = arguments { + guard let statementArgs = StatementArguments(arguments) else { + owsFail("Could not convert arguments.") + } + statementArguments = statementArgs + } + return InstalledStickerCursor(cursor: SDSSerialization.fetchCursor(sql: sql, + arguments: statementArguments, + transaction: transaction, + deserialize: InstalledStickerSerializer.sdsDeserialize)) + } +} + +// MARK: - SDSSerializer + +// The SDSSerializer protocol specifies how to insert and update the +// row that corresponds to this model. +class InstalledStickerSerializer: SDSSerializer { + + private let model: InstalledSticker + public required init(model: InstalledSticker) { + self.model = model + } + + public func serializableColumnTableMetadata() -> SDSTableMetadata { + return InstalledStickerSerializer.table + } + + public func insertColumnNames() -> [String] { + // When we insert a new row, we include the following columns: + // + // * "record type" + // * "unique id" + // * ...all columns that we set when updating. + return [ + InstalledStickerSerializer.recordTypeColumn.columnName, + uniqueIdColumnName() + ] + updateColumnNames() + + } + + public func insertColumnValues() -> [DatabaseValueConvertible] { + let result: [DatabaseValueConvertible] = [ + SDSRecordType.installedSticker.rawValue + ] + [uniqueIdColumnValue()] + updateColumnValues() + if OWSIsDebugBuild() { + if result.count != insertColumnNames().count { + owsFailDebug("Update mismatch: \(result.count) != \(insertColumnNames().count)") + } + } + return result + } + + public func updateColumnNames() -> [String] { + return [ + InstalledStickerSerializer.packIdColumn, + InstalledStickerSerializer.packKeyColumn, + InstalledStickerSerializer.stickerIdColumn + ].map { $0.columnName } + } + + public func updateColumnValues() -> [DatabaseValueConvertible] { + let result: [DatabaseValueConvertible] = [ + self.model.packId, + self.model.packKey, + self.model.stickerId + + ] + if OWSIsDebugBuild() { + if result.count != updateColumnNames().count { + owsFailDebug("Update mismatch: \(result.count) != \(updateColumnNames().count)") + } + } + return result + } + + public func uniqueIdColumnName() -> String { + return InstalledStickerSerializer.uniqueIdColumn.columnName + } + + // TODO: uniqueId is currently an optional on our models. + // We should probably make the return type here String? + public func uniqueIdColumnValue() -> DatabaseValueConvertible { + // FIXME remove force unwrap + return model.uniqueId! + } +} diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledSticker.h b/SignalServiceKit/src/Messages/Stickers/InstalledSticker.h new file mode 100644 index 0000000000..6e28b089c3 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledSticker.h @@ -0,0 +1,40 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "TSYapDatabaseObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@class SDSAnyReadTransaction; + +@interface InstalledSticker : TSYapDatabaseObject + +@property (nonatomic, readonly) NSData *packId; +@property (nonatomic, readonly) NSData *packKey; +@property (nonatomic, readonly) UInt32 stickerId; + +- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey stickerId:(UInt32)stickerId; + +// --- 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)initWithUniqueId:(NSString *)uniqueId + packId:(NSData *)packId + packKey:(NSData *)packKey + stickerId:(unsigned int)stickerId +NS_SWIFT_NAME(init(uniqueId:packId:packKey:stickerId:)); + +// clang-format on + +// --- CODE GENERATION MARKER + ++ (NSString *)uniqueIdForPackId:(NSData *)packId stickerId:(UInt32)stickerId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledSticker.m b/SignalServiceKit/src/Messages/Stickers/InstalledSticker.m new file mode 100644 index 0000000000..f345d50bc6 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledSticker.m @@ -0,0 +1,66 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "InstalledSticker.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation InstalledSticker + +- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey stickerId:(UInt32)stickerId +{ + OWSAssertDebug(packId.length > 0); + OWSAssertDebug(packKey.length > 0); + + self = [super initWithUniqueId:[InstalledSticker uniqueIdForPackId:packId stickerId:stickerId]]; + + if (!self) { + return self; + } + + _packId = packId; + _packKey = packKey; + _stickerId = stickerId; + + return self; +} + +// --- 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)initWithUniqueId:(NSString *)uniqueId + packId:(NSData *)packId + packKey:(NSData *)packKey + stickerId:(unsigned int)stickerId +{ + self = [super initWithUniqueId:uniqueId]; + + if (!self) { + return self; + } + + _packId = packId; + _packKey = packKey; + _stickerId = stickerId; + + return self; +} + +// clang-format on + +// --- CODE GENERATION MARKER + ++ (NSString *)uniqueIdForPackId:(NSData *)packId stickerId:(UInt32)stickerId +{ + return [NSString stringWithFormat:@"%@.%lu", packId.hexadecimalString, (unsigned long)stickerId]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack+SDS.swift b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack+SDS.swift new file mode 100644 index 0000000000..852c1fd8f1 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack+SDS.swift @@ -0,0 +1,243 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation +import GRDBCipher +import SignalCoreKit + +// NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py. +// Do not manually edit it, instead run `sds_codegen.sh`. + +// MARK: - SDSSerializable + +extension InstalledStickerPack: SDSSerializable { + 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 InstalledStickerPackSerializer(model: self) + } + } +} + +// MARK: - Table Metadata + +extension InstalledStickerPackSerializer { + + // This defines all of the columns used in the table + // where this model (and any subclasses) are persisted. + static let recordTypeColumn = SDSColumnMetadata(columnName: "recordType", columnType: .int, columnIndex: 0) + static let uniqueIdColumn = SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, columnIndex: 1) + // Base class properties + static let packIdColumn = SDSColumnMetadata(columnName: "packId", columnType: .blob, columnIndex: 2) + static let packKeyColumn = SDSColumnMetadata(columnName: "packKey", columnType: .blob, columnIndex: 3) + + // TODO: We should decide on a naming convention for + // tables that store models. + public static let table = SDSTableMetadata(tableName: "model_InstalledStickerPack", columns: [ + recordTypeColumn, + uniqueIdColumn, + packIdColumn, + packKeyColumn + ]) + +} + +// MARK: - Deserialization + +extension InstalledStickerPackSerializer { + // 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 sdsDeserialize(statement: SelectStatement) throws -> InstalledStickerPack { + + if OWSIsDebugBuild() { + guard statement.columnNames == table.selectColumnNames else { + owsFailDebug("Unexpected columns: \(statement.columnNames) != \(table.selectColumnNames)") + throw SDSError.invalidResult + } + } + + // SDSDeserializer is used to convert column values into Swift values. + let deserializer = SDSDeserializer(sqliteStatement: statement.sqliteStatement) + let recordTypeValue = try deserializer.int(at: 0) + guard let recordType = SDSRecordType(rawValue: UInt(recordTypeValue)) else { + owsFailDebug("Invalid recordType: \(recordTypeValue)") + throw SDSError.invalidResult + } + switch recordType { + case .installedStickerPack: + + let uniqueId = try deserializer.string(at: uniqueIdColumn.columnIndex) + let packId = try deserializer.blob(at: packIdColumn.columnIndex) + let packKey = try deserializer.blob(at: packKeyColumn.columnIndex) + + return InstalledStickerPack(uniqueId: uniqueId, + packId: packId, + packKey: packKey) + + default: + owsFail("Invalid record type \(recordType)") + } + } +} + +// MARK: - Save/Remove/Update + +@objc +extension InstalledStickerPack { + @objc + public func anySave(transaction: SDSAnyWriteTransaction) { + switch transaction.writeTransaction { + case .yapWrite(let ydbTransaction): + save(with: ydbTransaction) + case .grdbWrite(let grdbTransaction): + SDSSerialization.save(entity: self, transaction: grdbTransaction) + } + } + + @objc + public func anyRemove(transaction: SDSAnyWriteTransaction) { + switch transaction.writeTransaction { + case .yapWrite(let ydbTransaction): + remove(with: ydbTransaction) + case .grdbWrite(let grdbTransaction): + SDSSerialization.delete(entity: self, transaction: grdbTransaction) + } + } +} + +// MARK: - InstalledStickerPackCursor + +@objc +public class InstalledStickerPackCursor: NSObject { + private let cursor: SDSCursor + + init(cursor: SDSCursor) { + self.cursor = cursor + } + + // TODO: Revisit error handling in this class. + public func next() throws -> InstalledStickerPack? { + return try cursor.next() + } + + public func all() throws -> [InstalledStickerPack] { + return try cursor.all() + } +} + +// MARK: - Obj-C Fetch + +// TODO: We may eventually want to define some combination of: +// +// * fetchCursor, fetchOne, fetchAll, etc. (ala GRDB) +// * Optional "where clause" parameters for filtering. +// * Async flavors with completions. +// +// TODO: I've defined flavors that take a read transaction. +// Or we might take a "connection" if we end up having that class. +@objc +extension InstalledStickerPack { + public class func grdbFetchCursor(transaction: GRDBReadTransaction) -> InstalledStickerPackCursor { + return InstalledStickerPackCursor(cursor: SDSSerialization.fetchCursor(tableMetadata: InstalledStickerPackSerializer.table, + transaction: transaction, + deserialize: InstalledStickerPackSerializer.sdsDeserialize)) + } +} + +// MARK: - Swift Fetch + +extension InstalledStickerPack { + public class func grdbFetchCursor(sql: String, + arguments: [DatabaseValueConvertible]?, + transaction: GRDBReadTransaction) -> InstalledStickerPackCursor { + var statementArguments: StatementArguments? + if let arguments = arguments { + guard let statementArgs = StatementArguments(arguments) else { + owsFail("Could not convert arguments.") + } + statementArguments = statementArgs + } + return InstalledStickerPackCursor(cursor: SDSSerialization.fetchCursor(sql: sql, + arguments: statementArguments, + transaction: transaction, + deserialize: InstalledStickerPackSerializer.sdsDeserialize)) + } +} + +// MARK: - SDSSerializer + +// The SDSSerializer protocol specifies how to insert and update the +// row that corresponds to this model. +class InstalledStickerPackSerializer: SDSSerializer { + + private let model: InstalledStickerPack + public required init(model: InstalledStickerPack) { + self.model = model + } + + public func serializableColumnTableMetadata() -> SDSTableMetadata { + return InstalledStickerPackSerializer.table + } + + public func insertColumnNames() -> [String] { + // When we insert a new row, we include the following columns: + // + // * "record type" + // * "unique id" + // * ...all columns that we set when updating. + return [ + InstalledStickerPackSerializer.recordTypeColumn.columnName, + uniqueIdColumnName() + ] + updateColumnNames() + + } + + public func insertColumnValues() -> [DatabaseValueConvertible] { + let result: [DatabaseValueConvertible] = [ + SDSRecordType.installedStickerPack.rawValue + ] + [uniqueIdColumnValue()] + updateColumnValues() + if OWSIsDebugBuild() { + if result.count != insertColumnNames().count { + owsFailDebug("Update mismatch: \(result.count) != \(insertColumnNames().count)") + } + } + return result + } + + public func updateColumnNames() -> [String] { + return [ + InstalledStickerPackSerializer.packIdColumn, + InstalledStickerPackSerializer.packKeyColumn + ].map { $0.columnName } + } + + public func updateColumnValues() -> [DatabaseValueConvertible] { + let result: [DatabaseValueConvertible] = [ + self.model.packId, + self.model.packKey + + ] + if OWSIsDebugBuild() { + if result.count != updateColumnNames().count { + owsFailDebug("Update mismatch: \(result.count) != \(updateColumnNames().count)") + } + } + return result + } + + public func uniqueIdColumnName() -> String { + return InstalledStickerPackSerializer.uniqueIdColumn.columnName + } + + // TODO: uniqueId is currently an optional on our models. + // We should probably make the return type here String? + public func uniqueIdColumnValue() -> DatabaseValueConvertible { + // FIXME remove force unwrap + return model.uniqueId! + } +} diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.h b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.h new file mode 100644 index 0000000000..ff6f9ad208 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.h @@ -0,0 +1,36 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "TSYapDatabaseObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface InstalledStickerPack : TSYapDatabaseObject + +@property (nonatomic, readonly) NSData *packId; +@property (nonatomic, readonly) NSData *packKey; + +- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey; + +// --- 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)initWithUniqueId:(NSString *)uniqueId + packId:(NSData *)packId + packKey:(NSData *)packKey +NS_SWIFT_NAME(init(uniqueId:packId:packKey:)); + +// clang-format on + +// --- CODE GENERATION MARKER + ++ (NSString *)uniqueIdForPackId:(NSData *)packId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.m b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.m new file mode 100644 index 0000000000..66b19dde59 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledStickerPack.m @@ -0,0 +1,63 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "InstalledStickerPack.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation InstalledStickerPack + +- (instancetype)initWithPackId:(NSData *)packId packKey:(NSData *)packKey +{ + OWSAssertDebug(packId.length > 0); + OWSAssertDebug(packKey.length > 0); + + self = [super initWithUniqueId:[InstalledStickerPack uniqueIdForPackId:packId]]; + + if (!self) { + return self; + } + + _packId = packId; + _packKey = packKey; + + return self; +} + +// --- 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)initWithUniqueId:(NSString *)uniqueId + packId:(NSData *)packId + packKey:(NSData *)packKey +{ + self = [super initWithUniqueId:uniqueId]; + + if (!self) { + return self; + } + + _packId = packId; + _packKey = packKey; + + return self; +} + +// clang-format on + +// --- CODE GENERATION MARKER + ++ (NSString *)uniqueIdForPackId:(NSData *)packId +{ + return packId.hexadecimalString; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Stickers/InstalledStickers.swift b/SignalServiceKit/src/Messages/Stickers/InstalledStickers.swift new file mode 100644 index 0000000000..fe917faea9 --- /dev/null +++ b/SignalServiceKit/src/Messages/Stickers/InstalledStickers.swift @@ -0,0 +1,113 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +@objc +public class InstalledStickers: NSObject { + + private static var databaseStorage: SDSDatabaseStorage { + return SDSDatabaseStorage.shared + } + + public static let collection = "InstalledStickers" + + private let store = SDSKeyValueStore(collection: InstalledStickers.collection) + + private override init() {} + + // MARK: - Paths + + @objc + public class func cacheDirUrl() -> URL { + var url = URL(fileURLWithPath: OWSFileSystem.appSharedDataDirectoryPath()) + url.appendPathComponent("InstalledStickers") + OWSFileSystem.ensureDirectoryExists(url.path) + return url + } + + private class func stickerUrl(packId: Data, + stickerId: UInt32) -> URL { + assert(packId.count > 0) + + let uniqueId = InstalledSticker.uniqueId(forPackId: packId, stickerId: stickerId) + + var url = cacheDirUrl() + // All stickers are .webp. + url.appendPathComponent("\(uniqueId).webp") + return url + } + + // MARK: - Internal Methods + + // MARK: - + + @objc + public class func fetchInstalledSticker(packId: Data, + stickerId: UInt32, + transaction: SDSAnyReadTransaction) -> InstalledSticker? { + assert(packId.count > 0) + + let uniqueId = InstalledSticker.uniqueId(forPackId: packId, stickerId: stickerId) + + switch transaction.readTransaction { + case .yapRead(let ydbTransaction): + return InstalledSticker.fetch(uniqueId: uniqueId, transaction: ydbTransaction) + case .grdbRead(let grdbTransaction): + let tableMetadata = InstalledStickerSerializer.table + let columnNames: [String] = tableMetadata.selectColumnNames + let columnsSQL: String = columnNames.map { $0.quotedDatabaseIdentifier }.joined(separator: ", ") + let tableName: String = tableMetadata.tableName + let uniqueIdColumnName: String = InstalledStickerSerializer.uniqueIdColumn.columnName + let sql: String = "SELECT \(columnsSQL) FROM \(tableName.quotedDatabaseIdentifier) WHERE \(uniqueIdColumnName.quotedDatabaseIdentifier) == ?" + + let cursor = InstalledSticker.grdbFetchCursor(sql: sql, + arguments: [uniqueId], + transaction: grdbTransaction) + do { + return try cursor.next() + } catch { + owsFailDebug("error: \(error)") + return nil + } + } + } + + @objc + public class func installSticker(packId: Data, + packKey: Data, + stickerId: UInt32, + stickerData: Data) { + assert(packId.count > 0) + assert(packKey.count > 0) + assert(stickerData.count > 0) + + var result: InstalledSticker? + databaseStorage.readSwallowingErrors { (transaction) in + if let installedSticker = fetchInstalledSticker(packId: packId, stickerId: stickerId, transaction: transaction) { + result = installedSticker + } + } + if result != nil { + // Sticker already installed, skip. + return + } + + DispatchQueue.global().async { + let url = stickerUrl(packId: packId, stickerId: stickerId) + do { + try stickerData.write(to: url, options: .atomic) + } catch let error as NSError { + owsFailDebug("File write failed: \(error)") + return + } + + databaseStorage.writeSwallowingErrors { (transaction) in + let installedSticker = InstalledSticker(packId: packId, packKey: packKey, stickerId: stickerId) + installedSticker.anySave(transaction: transaction) + } + } + } +} diff --git a/SignalServiceKit/src/Messages/Interactions/MessageSticker.swift b/SignalServiceKit/src/Messages/Stickers/MessageSticker.swift similarity index 100% rename from SignalServiceKit/src/Messages/Interactions/MessageSticker.swift rename to SignalServiceKit/src/Messages/Stickers/MessageSticker.swift diff --git a/SignalServiceKit/src/SignalServiceKit.h b/SignalServiceKit/src/SignalServiceKit.h index 8240a44fde..fa4908b4c8 100644 --- a/SignalServiceKit/src/SignalServiceKit.h +++ b/SignalServiceKit/src/SignalServiceKit.h @@ -5,18 +5,20 @@ // Any Obj-c used by SSK Swift must be imported. #import #import +#import +#import #import +#import #import +#import #import +#import #import #import +#import #import #import #import #import #import #import -#import -#import -#import -#import diff --git a/SignalServiceKit/src/Storage/Database/SDSKeyValueStore.swift b/SignalServiceKit/src/Storage/Database/SDSKeyValueStore.swift index c1ffe57362..c3138a38fb 100644 --- a/SignalServiceKit/src/Storage/Database/SDSKeyValueStore.swift +++ b/SignalServiceKit/src/Storage/Database/SDSKeyValueStore.swift @@ -18,10 +18,6 @@ public class SDSKeyValueStore: NSObject { return SDSDatabaseStorage.shared } - private var primaryStorage: OWSPrimaryStorage { - return OWSPrimaryStorage.shared() - } - // By default, all reads/writes use this collection. public let collection: String diff --git a/SignalServiceKit/src/Storage/Database/SDSRecordType.swift b/SignalServiceKit/src/Storage/Database/SDSRecordType.swift index f4200ff619..072fec2515 100644 --- a/SignalServiceKit/src/Storage/Database/SDSRecordType.swift +++ b/SignalServiceKit/src/Storage/Database/SDSRecordType.swift @@ -11,6 +11,8 @@ import SignalCoreKit @objc public enum SDSRecordType: UInt { + case installedSticker = 28 + case installedStickerPack = 27 case addToContactsOfferMessage = 23 case addToProfileWhitelistOfferMessage = 7 case contactOffersInteraction = 21