Implement attachment downloading

pull/313/head
nielsandriesse 4 years ago
parent 961878f74c
commit b218a16b05

@ -69,9 +69,11 @@ target 'SessionMessagingKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/signalapp/YapDatabase.git', branch: 'signal-release', :inhibit_warnings => true
end
target 'SessionProtocolKit' do
@ -95,8 +97,10 @@ end
target 'SessionUtilitiesKit' do
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/signalapp/YapDatabase.git', branch: 'signal-release', :inhibit_warnings => true
end
post_install do |installer|

@ -230,6 +230,6 @@ SPEC CHECKSUMS:
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 3489ed70ea51f2bf705bf99703efc71d697de373
PODFILE CHECKSUM: 62df79698293257648cb6e60724f720f8477bd0f
COCOAPODS: 1.10.0.rc.1

@ -64,7 +64,7 @@
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/DataSource.h>
#import <SignalUtilitiesKit/MIMETypeUtil.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <SignalUtilitiesKit/NSData+Image.h>
#import <SignalUtilitiesKit/NSNotificationCenter+OWS.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
@ -72,7 +72,7 @@
#import <SignalUtilitiesKit/OWSContactsOutputStream.h>
#import <SignalUtilitiesKit/OWSDispatch.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/OWSIdentityManager.h>
#import <SignalUtilitiesKit/OWSMediaGalleryFinder.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage+SessionStore.h>

@ -7,7 +7,7 @@
#import "OWSProgressView.h"
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/AppContext.h>
#import <SessionUtilitiesKit/AppContext.h>
#import <SignalUtilitiesKit/OWSUploadOperation.h>
#import <SignalUtilitiesKit/TSAttachmentStream.h>

@ -10,7 +10,7 @@
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/MimeTypeUtil.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/TSAttachmentStream.h>
#import <SignalCoreKit/NSString+OWS.h>

@ -50,7 +50,7 @@
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/UIUtil.h>
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SignalUtilitiesKit/MIMETypeUtil.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
#import <SignalUtilitiesKit/OWSAttachmentDownloads.h>
#import <SignalUtilitiesKit/OWSBlockingManager.h>

@ -2,7 +2,7 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/AppContext.h>
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN

@ -12,7 +12,7 @@
#import <SignalCoreKit/Threading.h>
#import <SignalUtilitiesKit/OWSBackgroundTask.h>
#import <SignalUtilitiesKit/OWSError.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/TSAttachment.h>
#import <SignalUtilitiesKit/TSAttachmentStream.h>
#import <SignalUtilitiesKit/TSMessage.h>

@ -4,7 +4,7 @@
#import "OWSBackupIO.h"
#import <SignalCoreKit/Randomness.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
@import Compression;

@ -10,7 +10,7 @@
#import <PromiseKit/AnyPromise.h>
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalUtilitiesKit/OWSBackgroundTask.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/TSAttachment.h>
#import <SignalUtilitiesKit/TSMessage.h>
#import <SignalUtilitiesKit/TSThread.h>

@ -13,7 +13,7 @@
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>
#import <SignalUtilitiesKit/UIView+OWS.h>
#import <SignalUtilitiesKit/MIMETypeUtil.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
NS_ASSUME_NONNULL_BEGIN

@ -10,7 +10,7 @@
#import <SignalUtilitiesKit/AppReadiness.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SignalUtilitiesKit/OWSPrimaryStorage.h>
#import <SignalUtilitiesKit/TSAttachmentStream.h>
#import <SignalUtilitiesKit/TSInteraction.h>

@ -1,29 +1,85 @@
import Foundation
import SessionUtilitiesKit
// TODO: Implementation
import SignalCoreKit
public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
public var delegate: JobDelegate?
private let attachmentID: String
public var id: String?
public var failureCount: UInt = 0
public enum Error : LocalizedError {
case noAttachment
public var errorDescription: String? {
switch self {
case .noAttachment: return "No such attachment."
}
}
}
// MARK: Settings
public class var collection: String { return "AttachmentDownloadJobCollection" }
public static let maxFailureCount: UInt = 20
// MARK: Initialization
public init(attachmentID: String) {
self.attachmentID = attachmentID
}
// MARK: Coding
public init?(coder: NSCoder) { }
public init?(coder: NSCoder) {
guard let attachmentID = coder.decodeObject(forKey: "attachmentID") as! String? else { return nil }
self.attachmentID = attachmentID
}
public func encode(with coder: NSCoder) { }
public func encode(with coder: NSCoder) {
coder.encode(attachmentID, forKey: "attachmentID")
}
// MARK: Running
public func execute() { }
public func execute() {
guard let pointer = TSAttachmentPointer.fetch(uniqueId: attachmentID) else {
return handleFailure(error: Error.noAttachment)
}
let temporaryFilePath = URL(fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth() + UUID().uuidString)
FileServerAPI.downloadAttachment(from: pointer.downloadURL).done(on: DispatchQueue.global(qos: .userInitiated)) { data in // Intentionally capture self
do {
try data.write(to: temporaryFilePath, options: .atomic)
} catch {
return self.handleFailure(error: error)
}
let plaintext: Data
if let key = pointer.encryptionKey, let digest = pointer.digest {
do {
plaintext = try Cryptography.decryptAttachment(data, withKey: key, digest: digest, unpaddedSize: pointer.byteCount)
} catch {
return self.handleFailure(error: error)
}
} else {
plaintext = data // Open group attachments are unencrypted
}
let stream = TSAttachmentStream(pointer: pointer)
do {
try stream.write(plaintext)
} catch {
return self.handleFailure(error: error)
}
OWSFileSystem.deleteFile(temporaryFilePath.absoluteString)
Configuration.shared.storage.withAsync({ transaction in
stream.save(with: transaction as! YapDatabaseReadWriteTransaction)
// TODO: Update the message
}, completion: { })
}.catch(on: DispatchQueue.global()) { error in
self.handleFailure(error: error)
}
}
private func handleSuccess() {
delegate?.handleJobSucceeded(self)
}
private func handleFailure(error: Error) {
private func handleFailure(error: Swift.Error) {
delegate?.handleJobFailed(self, with: error)
}
}

@ -6,10 +6,14 @@ public final class JobQueue : NSObject, JobDelegate {
@objc public static let shared = JobQueue()
@objc public func add(_ job: Job, using transaction: Any) {
addWithoutExecuting(job, using: transaction)
job.execute()
}
@objc public func addWithoutExecuting(_ job: Job, using transaction: Any) {
job.id = String(NSDate.millisecondTimestamp())
Configuration.shared.storage.persist(job, using: transaction)
job.delegate = self
job.execute()
}
@objc public func resumePendingJobs() {

@ -75,12 +75,6 @@ private struct OWSThumbnailRequest {
// arrive so that we prioritize the most recent view state.
private var thumbnailRequestStack = [OWSThumbnailRequest]()
private override init() {
super.init()
SwiftSingletons.register(self)
}
private func canThumbnailAttachment(attachment: TSAttachmentStream) -> Bool {
return attachment.isImage || attachment.isAnimated || attachment.isVideo
}
@ -117,8 +111,6 @@ private struct OWSThumbnailRequest {
thumbnailRequest.success(loadedThumbnail)
}
} catch {
Logger.error("Could not create thumbnail: \(error)")
DispatchQueue.global().async {
thumbnailRequest.failure(error)
}
@ -146,8 +138,6 @@ private struct OWSThumbnailRequest {
return OWSLoadedThumbnail(image: image, filePath: thumbnailPath)
}
Logger.verbose("Creating thumbnail of size: \(thumbnailRequest.thumbnailDimensionPoints)")
let thumbnailDirPath = (thumbnailPath as NSString).deletingLastPathComponent
guard OWSFileSystem.ensureDirectoryExists(thumbnailDirPath) else {
throw OWSThumbnailError.failure(description: "Could not create attachment's thumbnail directory.")

@ -1,13 +1,8 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/TSYapDatabaseObject.h>
#import <SessionUtilitiesKit/TSYapDatabaseObject.h>
NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentPointer;
@class TSMessage;
typedef NS_ENUM(NSUInteger, TSAttachmentType) {
TSAttachmentTypeDefault = 0,
@ -45,7 +40,6 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly, nullable) NSString *caption;
@property (nonatomic, nullable) NSString *albumMessageId;
- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction;
// `migrateAlbumMessageId` is only used in the migration to the new multi-attachment message scheme,
// and shouldn't be used as a general purpose setter. Instead, `albumMessageId` should be passed as

@ -1,13 +1,7 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "TSAttachment.h"
#import "MIMETypeUtil.h"
#import "NSString+SSK.h"
#import "TSAttachmentPointer.h"
#import "TSMessage.h"
#import <SessionProtocolKit/SessionProtocolKit.h>
#import <SignalCoreKit/NSString+OWS.h>
NS_ASSUME_NONNULL_BEGIN
@ -16,9 +10,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
@interface TSAttachment ()
@property (nonatomic, readonly) NSUInteger attachmentSchemaVersion;
@property (nonatomic, nullable) NSString *sourceFilename;
@property (nonatomic) NSString *contentType;
@end
@ -35,17 +27,9 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
caption:(nullable NSString *)caption
albumMessageId:(nullable NSString *)albumMessageId
{
OWSAssertDebug(serverId > 0);
if (byteCount <= 0) {
// This will fail with legacy iOS clients which don't upload attachment size.
OWSLogWarn(@"Missing byteCount for attachment with serverId: %lld", serverId);
}
if (contentType.length < 1) {
OWSLogWarn(@"incoming attachment has invalid content type");
contentType = OWSMimeTypeApplicationOctetStream;
}
OWSAssertDebug(contentType.length > 0);
self = [super init];
if (!self) {
@ -73,13 +57,9 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
caption:(nullable NSString *)caption
albumMessageId:(nullable NSString *)albumMessageId
{
OWSAssertDebug(uniqueId.length > 0);
if (contentType.length < 1) {
OWSLogWarn(@"incoming attachment has invalid content type");
contentType = OWSMimeTypeApplicationOctetStream;
}
OWSAssertDebug(contentType.length > 0);
// If saved, this AttachmentPointer would replace the AttachmentStream in the attachments collection.
// However we only use this AttachmentPointer should only be used during the export process so it
@ -108,17 +88,13 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
albumMessageId:(nullable NSString *)albumMessageId
{
if (contentType.length < 1) {
OWSLogWarn(@"outgoing attachment has invalid content type");
contentType = OWSMimeTypeApplicationOctetStream;
}
OWSAssertDebug(contentType.length > 0);
self = [super init];
if (!self) {
return self;
}
OWSLogVerbose(@"init attachment with uniqueId: %@", self.uniqueId);
_contentType = contentType;
_byteCount = byteCount;
@ -135,15 +111,6 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
// that represent downloaded incoming attachments.
- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer
{
if (!pointer.lazyRestoreFragment) {
OWSAssertDebug(pointer.serverId > 0);
if (pointer.byteCount <= 0) {
// This will fail with legacy iOS clients which don't upload attachment size.
OWSLogWarn(@"Missing pointer.byteCount for attachment with serverId: %lld", pointer.serverId);
}
}
OWSAssertDebug(pointer.contentType.length > 0);
// Once saved, this AttachmentStream will replace the AttachmentPointer in the attachments collection.
self = [super initWithUniqueId:pointer.uniqueId];
if (!self) {
@ -156,8 +123,6 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
_sourceFilename = pointer.sourceFilename;
NSString *contentType = pointer.contentType;
if (contentType.length < 1) {
OWSLogWarn(@"incoming attachment has invalid content type");
contentType = OWSMimeTypeApplicationOctetStream;
}
_contentType = contentType;
@ -184,12 +149,9 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
if (!_sourceFilename) {
// renamed _filename to _sourceFilename
_sourceFilename = [coder decodeObjectForKey:@"filename"];
OWSAssertDebug(!_sourceFilename || [_sourceFilename isKindOfClass:[NSString class]]);
}
if (_contentType.length < 1) {
OWSLogWarn(@"legacy attachment has invalid content type");
_contentType = OWSMimeTypeApplicationOctetStream;
}
@ -284,14 +246,6 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
#pragma mark - Relationships
- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction
{
if (self.albumMessageId == nil) {
return nil;
}
return [TSMessage fetchObjectWithUniqueID:self.albumMessageId transaction:transaction];
}
- (void)migrateAlbumMessageId:(NSString *)albumMesssageId
{
_albumMessageId = albumMesssageId;

@ -1,8 +1,4 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/TSAttachment.h>
#import <SessionMessagingKit/TSAttachment.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@ -39,9 +35,6 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
@property (nonatomic, readonly) CGSize mediaSize;
// Non-nil for attachments which need "lazy backup restore."
- (nullable OWSBackupFragment *)lazyRestoreFragment;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithServerId:(UInt64)serverId
@ -64,12 +57,6 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
(NSArray<SNProtoAttachmentPointer *> *)attachmentProtos
albumMessage:(TSMessage *)message;
#pragma mark - Update With... Methods
// Marks attachment as needing "lazy backup restore."
- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment
transaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

@ -1,14 +1,9 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "TSAttachmentPointer.h"
#import "OWSBackupFragment.h"
#import "TSAttachmentStream.h"
#import <SignalUtilitiesKit/MIMETypeUtil.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseTransaction.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -86,8 +81,6 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initForRestoreWithAttachmentStream:(TSAttachmentStream *)attachmentStream
{
OWSAssertDebug(attachmentStream);
self = [super initForRestoreWithUniqueId:attachmentStream.uniqueId
contentType:attachmentStream.contentType
sourceFilename:attachmentStream.sourceFilename
@ -109,21 +102,14 @@ NS_ASSUME_NONNULL_BEGIN
albumMessage:(nullable TSMessage *)albumMessage
{
if (attachmentProto.id < 1) {
OWSFailDebug(@"Invalid attachment id.");
return nil;
}
/*
if (attachmentProto.key.length < 1) {
OWSFailDebug(@"Invalid attachment key.");
return 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];
@ -148,11 +134,6 @@ NS_ASSUME_NONNULL_BEGIN
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) {
@ -166,22 +147,17 @@ NS_ASSUME_NONNULL_BEGIN
contentType:contentType
sourceFilename:fileName
caption:caption
albumMessageId:albumMessageId
albumMessageId:0
attachmentType:attachmentType
mediaSize:mediaSize];
pointer.downloadURL = attachmentProto.url; // Loki
pointer.downloadURL = attachmentProto.url;
return pointer;
}
+ (NSArray<TSAttachmentPointer *> *)attachmentPointersFromProtos:
(NSArray<SNProtoAttachmentPointer *> *)attachmentProtos
+ (NSArray<TSAttachmentPointer *> *)attachmentPointersFromProtos:(NSArray<SNProtoAttachmentPointer *> *)attachmentProtos
albumMessage:(TSMessage *)albumMessage
{
OWSAssertDebug(attachmentProtos);
OWSAssertDebug(albumMessage);
NSMutableArray *attachmentPointers = [NSMutableArray new];
for (SNProtoAttachmentPointer *attachmentProto in attachmentProtos) {
TSAttachmentPointer *_Nullable attachmentPointer =
@ -203,63 +179,13 @@ NS_ASSUME_NONNULL_BEGIN
// Legacy instances of TSAttachmentPointer apparently used the serverId as their
// uniqueId.
if (attachmentSchemaVersion < 2 && self.serverId == 0) {
OWSAssertDebug([self isDecimalNumberText:self.uniqueId]);
if ([self isDecimalNumberText:self.uniqueId]) {
// For legacy instances, try to parse the serverId from the uniqueId.
self.serverId = (UInt64)[self.uniqueId integerValue];
} else {
OWSLogError(@"invalid legacy attachment uniqueId: %@.", self.uniqueId);
}
}
}
- (nullable OWSBackupFragment *)lazyRestoreFragment
{
if (!self.lazyRestoreFragmentId) {
return nil;
}
OWSBackupFragment *_Nullable backupFragment =
[OWSBackupFragment fetchObjectWithUniqueID:self.lazyRestoreFragmentId];
OWSAssertDebug(backupFragment);
return backupFragment;
}
#pragma mark - Update With... Methods
- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(lazyRestoreFragment);
OWSAssertDebug(transaction);
if (!lazyRestoreFragment.uniqueId) {
// If metadata hasn't been saved yet, save now.
[lazyRestoreFragment saveWithTransaction:transaction];
OWSAssertDebug(lazyRestoreFragment.uniqueId);
}
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSAttachmentPointer *attachment) {
[attachment setLazyRestoreFragmentId:lazyRestoreFragment.uniqueId];
}];
}
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
#ifdef DEBUG
if (self.uniqueId.length > 0) {
id _Nullable oldObject = [transaction objectForKey:self.uniqueId inCollection:TSAttachment.collection];
if ([oldObject isKindOfClass:[TSAttachmentStream class]]) {
OWSFailDebug(@"We should never overwrite a TSAttachmentStream with a TSAttachmentPointer.");
}
} else {
OWSFailDebug(@"Missing uniqueId.");
}
#endif
[super saveWithTransaction:transaction];
}
@end
NS_ASSUME_NONNULL_END

@ -1,9 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/DataSource.h>
#import <SignalUtilitiesKit/TSAttachment.h>
#import <SessionUtilitiesKit/DataSource.h>
#import <SessionMessagingKit/TSAttachment.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
@ -19,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSThumbnailSuccess)(UIImage *image);
typedef void (^OWSThumbnailFailure)(void);
@interface TSAttachmentStream : TSAttachment <SNAttachmentStream>
@interface TSAttachmentStream : TSAttachment
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithContentType:(NSString *)contentType

@ -1,16 +1,11 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "TSAttachmentStream.h"
#import "MIMETypeUtil.h"
#import "NSData+Image.h"
#import "OWSFileSystem.h"
#import "TSAttachmentPointer.h"
#import <AVFoundation/AVFoundation.h>
#import <SignalCoreKit/Threading.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -149,21 +144,17 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
sourceFilename:self.sourceFilename
inFolder:attachmentsFolder];
if (!filePath) {
OWSFailDebug(@"Could not generate path for attachment.");
return;
}
if (![filePath hasPrefix:attachmentsFolder]) {
OWSFailDebug(@"Attachment paths should all be in the attachments folder.");
return;
}
NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length];
if (localRelativeFilePath.length < 1) {
OWSFailDebug(@"Empty local relative attachment paths.");
return;
}
self.localRelativeFilePath = localRelativeFilePath;
OWSAssertDebug(self.originalFilePath);
}
#pragma mark - File Management
@ -173,7 +164,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
*error = nil;
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Missing path for attachment.");
return nil;
}
return [NSData dataWithContentsOfFile:filePath options:0 error:error];
@ -181,28 +171,20 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (BOOL)writeData:(NSData *)data error:(NSError **)error
{
OWSAssertDebug(data);
*error = nil;
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Missing path for attachment.");
return NO;
}
OWSLogDebug(@"Writing attachment to file: %@", filePath);
return [data writeToFile:filePath options:0 error:error];
}
- (BOOL)writeDataSource:(DataSource *)dataSource
{
OWSAssertDebug(dataSource);
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Missing path for attachment.");
return NO;
}
OWSLogDebug(@"Writing attachment to file: %@", filePath);
return [dataSource writeToPath:filePath];
}
@ -218,8 +200,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
+ (nullable NSError *)migrateToSharedData
{
OWSLogInfo(@"");
return [OWSFileSystem moveAppFilePath:self.legacyAttachmentsDirPath
sharedDataFilePath:self.sharedDataAttachmentsDirPath];
}
@ -239,7 +219,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (nullable NSString *)originalFilePath
{
if (!self.localRelativeFilePath) {
OWSFailDebug(@"Attachment missing local file path.");
return nil;
}
@ -250,7 +229,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
{
NSString *filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Attachment missing local file path.");
return nil;
}
@ -268,7 +246,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (NSString *)thumbnailsDirPath
{
if (!self.localRelativeFilePath) {
OWSFailDebug(@"Attachment missing local file path.");
return nil;
}
@ -288,7 +265,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
{
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Missing path for attachment.");
return nil;
}
return [NSURL fileURLWithPath:filePath];
@ -300,30 +276,19 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
NSString *thumbnailsDirPath = self.thumbnailsDirPath;
if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailsDirPath]) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:thumbnailsDirPath error:&error];
if (error || !success) {
OWSLogError(@"remove thumbnails dir failed with: %@", error);
}
[[NSFileManager defaultManager] removeItemAtPath:thumbnailsDirPath error:&error];
}
NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath;
if (legacyThumbnailPath) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error];
if (error || !success) {
OWSLogError(@"remove legacy thumbnail failed with: %@", error);
}
[[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error];
}
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
OWSFailDebug(@"Missing path for attachment.");
return;
}
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error || !success) {
OWSLogError(@"remove file failed with: %@", error);
}
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
}
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
@ -353,13 +318,10 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (BOOL)isValidImage
{
OWSAssertDebug(self.isImage || self.isAnimated);
BOOL result;
BOOL didUpdateCache = NO;
@synchronized(self) {
if (!self.isValidImageCached) {
OWSLogVerbose(@"Updating isValidImageCached.");
self.isValidImageCached = @([NSData ows_isValidImageAtPath:self.originalFilePath
mimeType:self.contentType]);
didUpdateCache = YES;
@ -378,13 +340,10 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (BOOL)isValidVideo
{
OWSAssertDebug(self.isVideo);
BOOL result;
BOOL didUpdateCache = NO;
@synchronized(self) {
if (!self.isValidVideoCached) {
OWSLogVerbose(@"Updating isValidVideoCached.");
self.isValidVideoCached = @([OWSMediaUtils isValidVideoWithPath:self.originalFilePath]);
didUpdateCache = YES;
}
@ -423,16 +382,13 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (nullable NSData *)validStillImageData
{
if ([self isVideo]) {
OWSFailDebug(@"isVideo was unexpectedly true");
return nil;
}
if ([self isAnimated]) {
OWSFailDebug(@"isAnimated was unexpectedly true");
return nil;
}
if (![NSData ows_isValidImageAtPath:self.originalFilePath mimeType:self.contentType]) {
OWSFailDebug(@"skipping invalid image");
return nil;
}
@ -452,7 +408,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
maxDimension:ThumbnailDimensionPointsLarge()
error:&error];
if (error || !image) {
OWSLogError(@"Could not create video still: %@.", error);
return nil;
}
return image;
@ -468,15 +423,11 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
[fileManager contentsOfDirectoryAtURL:fileURL includingPropertiesForKeys:nil options:0 error:&error];
if (error) {
OWSFailDebug(@"failed to get contents of attachments folder: %@ with error: %@", self.attachmentsFolder, error);
return;
}
for (NSURL *url in contents) {
[fileManager removeItemAtURL:url error:&error];
if (error) {
OWSFailDebug(@"failed to remove item at path: %@ with error: %@", url, error);
}
}
}
@ -529,8 +480,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (CGSize)cachedMediaSize
{
OWSAssertDebug(self.shouldHaveImageSize);
@synchronized(self) {
if (self.cachedImageWidth && self.cachedImageHeight) {
return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue);
@ -544,8 +493,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (void)applyChangeAsyncToLatestCopyWithChangeBlock:(void (^)(TSAttachmentStream *))changeBlock
{
OWSAssertDebug(changeBlock);
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSString *collection = [TSAttachmentStream collection];
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
@ -555,9 +502,8 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
// _very_ rare.
//
// An exception is incoming group avatar updates which we don't ever save.
OWSLogWarn(@"Attachment not yet saved.");
} else if (![latestInstance isKindOfClass:[TSAttachmentStream class]]) {
OWSFailDebug(@"Attachment has unexpected type: %@", latestInstance.class);
// Shouldn't occur
} else {
changeBlock(latestInstance);
@ -570,9 +516,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
- (CGFloat)calculateAudioDurationSeconds
{
OWSAssertIsOnMainThread();
OWSAssertDebug([self isAudio]);
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.originalMediaURL error:&error];
if (error && [error.domain isEqualToString:NSOSStatusErrorDomain]
@ -583,15 +526,12 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
if (!error) {
return (CGFloat)[audioPlayer duration];
} else {
OWSLogError(@"Could not find audio duration: %@", self.originalMediaURL);
return 0;
}
}
- (CGFloat)audioDurationSeconds
{
OWSAssertIsOnMainThread();
if (self.cachedAudioDurationSeconds) {
return self.cachedAudioDurationSeconds.floatValue;
}
@ -686,7 +626,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailPath]) {
UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath];
if (!image) {
OWSFailDebug(@"couldn't load image.");
// Any time we return nil from this method we have to call the failure handler
// or else the caller waits for an async thumbnail
failure();
@ -699,7 +638,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
thumbnailDimensionPoints:thumbnailDimensionPoints
success:success
failure:^(NSError *error) {
OWSLogError(@"Failed to create thumbnail: %@", error);
failure();
}];
return nil;
@ -737,7 +675,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
{
OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync];
if (!loadedThumbnail) {
OWSLogInfo(@"Couldn't load small thumbnail sync.");
return nil;
}
return loadedThumbnail.image;
@ -747,13 +684,11 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
{
OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync];
if (!loadedThumbnail) {
OWSLogInfo(@"Couldn't load small thumbnail sync.");
return nil;
}
NSError *error;
NSData *_Nullable data = [loadedThumbnail dataAndReturnError:&error];
if (error || !data) {
OWSFailDebug(@"Couldn't load thumbnail data: %@", error);
return nil;
}
return data;
@ -769,7 +704,7 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
NSArray<NSString *> *_Nullable fileNames =
[[NSFileManager defaultManager] contentsOfDirectoryAtPath:thumbnailsDirPath error:&error];
if (error || !fileNames) {
OWSFailDebug(@"contentsOfDirectoryAtPath failed with error: %@", error);
// Do nothing