376 lines
14 KiB
Objective-C
376 lines
14 KiB
Objective-C
//
|
|
// Copyright 2017 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
#import "TSAttachmentPointer.h"
|
|
#import "TSAttachmentStream.h"
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSString *NSStringForTSAttachmentPointerState(TSAttachmentPointerState value)
|
|
{
|
|
switch (value) {
|
|
case TSAttachmentPointerStateEnqueued:
|
|
return @"Enqueued";
|
|
case TSAttachmentPointerStateDownloading:
|
|
return @"Downloading";
|
|
case TSAttachmentPointerStateFailed:
|
|
return @"Failed";
|
|
case TSAttachmentPointerStatePendingMessageRequest:
|
|
return @"PendingMessageRequest";
|
|
case TSAttachmentPointerStatePendingManualDownload:
|
|
return @"PendingManualDownload";
|
|
default:
|
|
OWSCFailDebug(@"Invalid value.");
|
|
return @"Invalid value";
|
|
}
|
|
}
|
|
|
|
static const NSUInteger kMaxAttachmentsPerDataMessage = 100;
|
|
|
|
@interface TSAttachmentStream (TSAttachmentPointer)
|
|
|
|
- (CGSize)cachedMediaSize;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface TSAttachmentPointer ()
|
|
|
|
@property (nonatomic) TSAttachmentPointerState state;
|
|
|
|
// Optional property. Only set for attachments which need "lazy backup restore."
|
|
@property (nonatomic, nullable) NSString *lazyRestoreFragmentId;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TSAttachmentPointer
|
|
|
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
|
{
|
|
self = [super initWithCoder:coder];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
// A TSAttachmentPointer is a yet-to-be-downloaded attachment.
|
|
// If this is an old TSAttachmentPointer from another session,
|
|
// we know that it failed to complete before the session completed.
|
|
if (![coder containsValueForKey:@"state"]) {
|
|
_state = TSAttachmentPointerStateFailed;
|
|
}
|
|
|
|
// Put a meaningless value into the meaningless field
|
|
_pointerType = 0;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)initWithServerId:(UInt64)serverId
|
|
cdnKey:(NSString *)cdnKey
|
|
cdnNumber:(UInt32)cdnNumber
|
|
key:(NSData *)key
|
|
digest:(nullable NSData *)digest
|
|
byteCount:(UInt32)byteCount
|
|
contentType:(NSString *)contentType
|
|
clientUuid:(NSUUID *)clientUuid
|
|
sourceFilename:(nullable NSString *)sourceFilename
|
|
caption:(nullable NSString *)caption
|
|
albumMessageId:(nullable NSString *)albumMessageId
|
|
attachmentType:(TSAttachmentType)attachmentType
|
|
mediaSize:(CGSize)mediaSize
|
|
blurHash:(nullable NSString *)blurHash
|
|
uploadTimestamp:(unsigned long long)uploadTimestamp
|
|
videoDuration:(nullable NSNumber *)videoDuration
|
|
{
|
|
self = [super initWithServerId:serverId
|
|
cdnKey:cdnKey
|
|
cdnNumber:cdnNumber
|
|
encryptionKey:key
|
|
byteCount:byteCount
|
|
contentType:contentType
|
|
clientUuid:clientUuid
|
|
sourceFilename:sourceFilename
|
|
caption:caption
|
|
attachmentType:attachmentType
|
|
albumMessageId:albumMessageId
|
|
blurHash:blurHash
|
|
uploadTimestamp:uploadTimestamp
|
|
videoDuration:videoDuration];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_digest = digest;
|
|
_state = TSAttachmentPointerStateEnqueued;
|
|
// Put a meaningless value into the meaningless field
|
|
_pointerType = 0;
|
|
_mediaSize = mediaSize;
|
|
|
|
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)initWithGrdbId:(int64_t)grdbId
|
|
uniqueId:(NSString *)uniqueId
|
|
albumMessageId:(nullable NSString *)albumMessageId
|
|
attachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion
|
|
attachmentType:(TSAttachmentType)attachmentType
|
|
blurHash:(nullable NSString *)blurHash
|
|
byteCount:(unsigned int)byteCount
|
|
caption:(nullable NSString *)caption
|
|
cdnKey:(NSString *)cdnKey
|
|
cdnNumber:(unsigned int)cdnNumber
|
|
clientUuid:(nullable NSString *)clientUuid
|
|
contentType:(NSString *)contentType
|
|
encryptionKey:(nullable NSData *)encryptionKey
|
|
serverId:(unsigned long long)serverId
|
|
sourceFilename:(nullable NSString *)sourceFilename
|
|
uploadTimestamp:(unsigned long long)uploadTimestamp
|
|
videoDuration:(nullable NSNumber *)videoDuration
|
|
digest:(nullable NSData *)digest
|
|
lazyRestoreFragmentId:(nullable NSString *)lazyRestoreFragmentId
|
|
mediaSize:(CGSize)mediaSize
|
|
pointerType:(NSUInteger)pointerType
|
|
state:(TSAttachmentPointerState)state
|
|
{
|
|
self = [super initWithGrdbId:grdbId
|
|
uniqueId:uniqueId
|
|
albumMessageId:albumMessageId
|
|
attachmentSchemaVersion:attachmentSchemaVersion
|
|
attachmentType:attachmentType
|
|
blurHash:blurHash
|
|
byteCount:byteCount
|
|
caption:caption
|
|
cdnKey:cdnKey
|
|
cdnNumber:cdnNumber
|
|
clientUuid:clientUuid
|
|
contentType:contentType
|
|
encryptionKey:encryptionKey
|
|
serverId:serverId
|
|
sourceFilename:sourceFilename
|
|
uploadTimestamp:uploadTimestamp
|
|
videoDuration:videoDuration];
|
|
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_digest = digest;
|
|
_lazyRestoreFragmentId = lazyRestoreFragmentId;
|
|
_mediaSize = mediaSize;
|
|
_pointerType = pointerType;
|
|
_state = state;
|
|
|
|
[self sdsFinalizeAttachmentPointer];
|
|
|
|
return self;
|
|
}
|
|
|
|
// clang-format on
|
|
|
|
// --- CODE GENERATION MARKER
|
|
|
|
- (void)sdsFinalizeAttachmentPointer
|
|
{
|
|
[self upgradeAttachmentSchemaVersionIfNecessary];
|
|
}
|
|
|
|
+ (nullable TSAttachmentPointer *)attachmentPointerFromProto:(SSKProtoAttachmentPointer *)attachmentProto
|
|
albumMessage:(nullable TSMessage *)albumMessage
|
|
{
|
|
// TODO: Use the cdnKey and cdnNumber to fetch the attachment
|
|
if (attachmentProto.cdnID < 1 && attachmentProto.cdnKey.length < 1) {
|
|
OWSFailDebug(@"Invalid attachment id.");
|
|
return nil;
|
|
}
|
|
if (attachmentProto.key.length < 1) {
|
|
OWSFailDebug(@"Invalid attachment key.");
|
|
return nil;
|
|
}
|
|
|
|
NSUUID *_Nullable clientUuid;
|
|
if (attachmentProto.hasClientUuid) {
|
|
clientUuid = [NSUUID fromData:attachmentProto.clientUuid];
|
|
} else {
|
|
clientUuid = nil;
|
|
}
|
|
|
|
NSString *_Nullable fileName = attachmentProto.fileName;
|
|
NSString *_Nullable contentType = attachmentProto.contentType;
|
|
if (contentType.length < 1) {
|
|
// Content type might not set if the sending client can't
|
|
// infer a MIME type from the file extension.
|
|
OWSLogWarn(@"Invalid attachment content type.");
|
|
NSString *_Nullable fileExtension = [fileName pathExtension].lowercaseString;
|
|
if (fileExtension.length > 0) {
|
|
contentType = [MimeTypeUtil mimeTypeForFileExtension:fileExtension];
|
|
}
|
|
if (contentType.length < 1) {
|
|
contentType = MimeTypeUtil.mimeTypeApplicationOctetStream;
|
|
}
|
|
}
|
|
|
|
// digest will be empty for old clients.
|
|
NSData *_Nullable digest = attachmentProto.hasDigest ? attachmentProto.digest : nil;
|
|
|
|
TSAttachmentType attachmentType = TSAttachmentTypeDefault;
|
|
if ([attachmentProto hasFlags]) {
|
|
UInt32 flags = attachmentProto.flags;
|
|
if ((flags & SSKProtoAttachmentPointerFlagsVoiceMessage) > 0) {
|
|
attachmentType = TSAttachmentTypeVoiceMessage;
|
|
} else if ((flags & SSKProtoAttachmentPointerFlagsBorderless) > 0) {
|
|
attachmentType = TSAttachmentTypeBorderless;
|
|
} else if ((flags & SSKProtoAttachmentPointerFlagsGif) > 0) {
|
|
attachmentType = TSAttachmentTypeGIF;
|
|
}
|
|
}
|
|
NSString *_Nullable caption;
|
|
if (attachmentProto.hasCaption) {
|
|
caption = attachmentProto.caption;
|
|
}
|
|
|
|
NSString *_Nullable albumMessageId;
|
|
if (albumMessage != nil) {
|
|
albumMessageId = albumMessage.uniqueId;
|
|
}
|
|
|
|
CGSize mediaSize = CGSizeZero;
|
|
if (attachmentProto.hasWidth && attachmentProto.hasHeight && attachmentProto.width > 0
|
|
&& attachmentProto.height > 0) {
|
|
mediaSize = CGSizeMake(attachmentProto.width, attachmentProto.height);
|
|
}
|
|
|
|
UInt64 serverId = attachmentProto.cdnID;
|
|
if (![SDS fitsInInt64:serverId]) {
|
|
OWSFailDebug(@"Invalid server id.");
|
|
return nil;
|
|
}
|
|
|
|
NSString *_Nullable blurHash;
|
|
if (contentType.length > 0 && [MimeTypeUtil isSupportedVisualMediaMimeType:contentType]
|
|
&& attachmentProto.hasBlurHash) {
|
|
blurHash = attachmentProto.blurHash;
|
|
if (![BlurHash isValidBlurHash:blurHash]) {
|
|
blurHash = nil;
|
|
}
|
|
}
|
|
|
|
unsigned long long uploadTimestamp = 0;
|
|
if (attachmentProto.hasUploadTimestamp) {
|
|
uploadTimestamp = attachmentProto.uploadTimestamp;
|
|
}
|
|
|
|
TSAttachmentPointer *pointer = [[TSAttachmentPointer alloc] initWithServerId:serverId
|
|
cdnKey:attachmentProto.cdnKey
|
|
cdnNumber:attachmentProto.cdnNumber
|
|
key:attachmentProto.key
|
|
digest:digest
|
|
byteCount:attachmentProto.size
|
|
contentType:contentType
|
|
clientUuid:clientUuid
|
|
sourceFilename:fileName
|
|
caption:caption
|
|
albumMessageId:albumMessageId
|
|
attachmentType:attachmentType
|
|
mediaSize:mediaSize
|
|
blurHash:blurHash
|
|
uploadTimestamp:uploadTimestamp
|
|
videoDuration:nil];
|
|
return pointer;
|
|
}
|
|
|
|
+ (NSArray<TSAttachmentPointer *> *)attachmentPointersFromProtos:
|
|
(NSArray<SSKProtoAttachmentPointer *> *)attachmentProtos
|
|
albumMessage:(TSMessage *)albumMessage
|
|
{
|
|
OWSAssertDebug(attachmentProtos);
|
|
OWSAssertDebug(albumMessage);
|
|
|
|
NSUInteger pointerCount = MIN(kMaxAttachmentsPerDataMessage, attachmentProtos.count);
|
|
|
|
NSMutableArray *attachmentPointers = [[NSMutableArray alloc] initWithCapacity:pointerCount];
|
|
for (NSUInteger i = 0; i < pointerCount; i = i + 1) {
|
|
SSKProtoAttachmentPointer *attachmentProto = attachmentProtos[i];
|
|
TSAttachmentPointer *_Nullable attachmentPointer = [self attachmentPointerFromProto:attachmentProto
|
|
albumMessage:albumMessage];
|
|
if (attachmentPointer) {
|
|
[attachmentPointers addObject:attachmentPointer];
|
|
}
|
|
}
|
|
return [attachmentPointers copy];
|
|
}
|
|
|
|
#pragma mark - Update With... Methods
|
|
|
|
- (void)anyWillInsertWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyWillInsertWithTransaction:transaction];
|
|
|
|
[self checkForStreamOverwrite:transaction];
|
|
}
|
|
|
|
- (void)anyWillUpdateWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyWillUpdateWithTransaction:transaction];
|
|
|
|
[self checkForStreamOverwrite:transaction];
|
|
}
|
|
|
|
- (void)checkForStreamOverwrite:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
#ifdef DEBUG
|
|
if (self.uniqueId.length > 0) {
|
|
TSAttachment *_Nullable oldObject = [TSAttachment anyFetchWithUniqueId:self.uniqueId transaction:transaction];
|
|
if ([oldObject isKindOfClass:[TSAttachmentStream class]]) {
|
|
OWSFailDebug(@"We should never overwrite a TSAttachmentStream with a TSAttachmentPointer.");
|
|
}
|
|
} else {
|
|
OWSFailDebug(@"Missing uniqueId.");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if TESTABLE_BUILD
|
|
- (void)setAttachmentPointerStateDebug:(TSAttachmentPointerState)state
|
|
{
|
|
self.state = state;
|
|
}
|
|
#endif
|
|
|
|
- (void)updateWithAttachmentPointerState:(TSAttachmentPointerState)state
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[self anyUpdateAttachmentPointerWithTransaction:transaction
|
|
block:^(TSAttachmentPointer *attachmentPointer) {
|
|
attachmentPointer.state = state;
|
|
}];
|
|
}
|
|
|
|
- (void)updateAttachmentPointerStateFrom:(TSAttachmentPointerState)oldState
|
|
to:(TSAttachmentPointerState)newState
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[self anyUpdateAttachmentPointerWithTransaction:transaction
|
|
block:^(TSAttachmentPointer *attachmentPointer) {
|
|
if (attachmentPointer.state == oldState) {
|
|
attachmentPointer.state = newState;
|
|
}
|
|
}];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|