Merge branch 'charlesmchen/attachmentFilenames'

pull/1/head
Matthew Chen 8 years ago
commit 2439752c20

@ -92,7 +92,7 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
digest:digest digest:digest
contentType:attachmentProto.contentType contentType:attachmentProto.contentType
relay:relay relay:relay
filename:attachmentProto.fileName sourceFilename:attachmentProto.fileName
attachmentType:attachmentType]; attachmentType:attachmentType];
[attachmentIds addObject:pointer.uniqueId]; [attachmentIds addObject:pointer.uniqueId];

@ -31,20 +31,20 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (atomic, readwrite) BOOL isDownloaded; @property (atomic, readwrite) BOOL isDownloaded;
@property (nonatomic) TSAttachmentType attachmentType; @property (nonatomic) TSAttachmentType attachmentType;
// Represents the "nominal" filename sent or received in the protos, // Represents the "source" filename sent or received in the protos,
// not the filename on disk. // not the filename on disk.
@property (nonatomic, readonly, nullable) NSString *filename; @property (nonatomic, readonly, nullable) NSString *sourceFilename;
// This constructor is used for new instances of TSAttachmentPointer, // This constructor is used for new instances of TSAttachmentPointer,
// i.e. undownloaded incoming attachments. // i.e. undownloaded incoming attachments.
- (instancetype)initWithServerId:(UInt64)serverId - (instancetype)initWithServerId:(UInt64)serverId
encryptionKey:(NSData *)encryptionKey encryptionKey:(NSData *)encryptionKey
contentType:(NSString *)contentType contentType:(NSString *)contentType
filename:(nullable NSString *)filename; sourceFilename:(nullable NSString *)sourceFilename;
// This constructor is used for new instances of TSAttachmentStream // This constructor is used for new instances of TSAttachmentStream
// that represent new, un-uploaded outgoing attachments. // that represent new, un-uploaded outgoing attachments.
- (instancetype)initWithContentType:(NSString *)contentType filename:(nullable NSString *)filename; - (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename;
// This constructor is used for new instances of TSAttachmentStream // This constructor is used for new instances of TSAttachmentStream
// that represent downloaded incoming attachments. // that represent downloaded incoming attachments.

@ -22,7 +22,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
- (instancetype)initWithServerId:(UInt64)serverId - (instancetype)initWithServerId:(UInt64)serverId
encryptionKey:(NSData *)encryptionKey encryptionKey:(NSData *)encryptionKey
contentType:(NSString *)contentType contentType:(NSString *)contentType
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
{ {
self = [super init]; self = [super init];
if (!self) { if (!self) {
@ -33,14 +33,14 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_encryptionKey = encryptionKey; _encryptionKey = encryptionKey;
_contentType = contentType; _contentType = contentType;
_attachmentSchemaVersion = TSAttachmentSchemaVersion; _attachmentSchemaVersion = TSAttachmentSchemaVersion;
_filename = filename; _sourceFilename = sourceFilename;
return self; return self;
} }
// This constructor is used for new instances of TSAttachmentStream // This constructor is used for new instances of TSAttachmentStream
// that represent new, un-uploaded outgoing attachments. // that represent new, un-uploaded outgoing attachments.
- (instancetype)initWithContentType:(NSString *)contentType filename:(nullable NSString *)filename - (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename
{ {
self = [super init]; self = [super init];
if (!self) { if (!self) {
@ -49,7 +49,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_contentType = contentType; _contentType = contentType;
_attachmentSchemaVersion = TSAttachmentSchemaVersion; _attachmentSchemaVersion = TSAttachmentSchemaVersion;
_filename = filename; _sourceFilename = sourceFilename;
return self; return self;
} }
@ -67,7 +67,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_serverId = pointer.serverId; _serverId = pointer.serverId;
_encryptionKey = pointer.encryptionKey; _encryptionKey = pointer.encryptionKey;
_contentType = pointer.contentType; _contentType = pointer.contentType;
_filename = pointer.filename; _sourceFilename = pointer.sourceFilename;
_attachmentSchemaVersion = TSAttachmentSchemaVersion; _attachmentSchemaVersion = TSAttachmentSchemaVersion;
return self; return self;
@ -85,6 +85,12 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_attachmentSchemaVersion = TSAttachmentSchemaVersion; _attachmentSchemaVersion = TSAttachmentSchemaVersion;
} }
if (!_sourceFilename) {
// renamed _filename to _sourceFilename
_sourceFilename = [coder decodeObjectForKey:@"filename"];
OWSAssert(!_sourceFilename || [_sourceFilename isKindOfClass:[NSString class]]);
}
return self; return self;
} }
@ -107,9 +113,9 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
return [NSString stringWithFormat:@"📽 %@", attachmentString]; return [NSString stringWithFormat:@"📽 %@", attachmentString];
} else if ([MIMETypeUtil isAudio:self.contentType]) { } else if ([MIMETypeUtil isAudio:self.contentType]) {
// a missing filename is the legacy way to determin if an audio attachment is a voice note vs. other arbitrary // a missing filename is the legacy way to determine if an audio attachment is
// audio attachments. // a voice note vs. other arbitrary audio attachments.
if (self.isVoiceMessage || !self.filename || self.filename.length == 0) { if (self.isVoiceMessage || !self.sourceFilename || self.sourceFilename.length == 0) {
attachmentString = NSLocalizedString(@"ATTACHMENT_TYPE_VOICE_MESSAGE", attachmentString = NSLocalizedString(@"ATTACHMENT_TYPE_VOICE_MESSAGE",
@"Short text label for a voice message attachment, used for thread preview and on lockscreen"); @"Short text label for a voice message attachment, used for thread preview and on lockscreen");
return [NSString stringWithFormat:@"🎤 %@", attachmentString]; return [NSString stringWithFormat:@"🎤 %@", attachmentString];

@ -24,7 +24,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
digest:(nullable NSData *)digest digest:(nullable NSData *)digest
contentType:(NSString *)contentType contentType:(NSString *)contentType
relay:(NSString *)relay relay:(NSString *)relay
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
attachmentType:(TSAttachmentType)attachmentType NS_DESIGNATED_INITIALIZER; attachmentType:(TSAttachmentType)attachmentType NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) NSString *relay; @property (nonatomic, readonly) NSString *relay;

@ -30,10 +30,10 @@ NS_ASSUME_NONNULL_BEGIN
digest:(nullable NSData *)digest digest:(nullable NSData *)digest
contentType:(NSString *)contentType contentType:(NSString *)contentType
relay:(NSString *)relay relay:(NSString *)relay
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
attachmentType:(TSAttachmentType)attachmentType attachmentType:(TSAttachmentType)attachmentType
{ {
self = [super initWithServerId:serverId encryptionKey:key contentType:contentType filename:filename]; self = [super initWithServerId:serverId encryptionKey:key contentType:contentType sourceFilename:sourceFilename];
if (!self) { if (!self) {
return self; return self;
} }

@ -10,12 +10,15 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentPointer; @class TSAttachmentPointer;
@class YapDatabaseReadWriteTransaction;
@interface TSAttachmentStream : TSAttachment @interface TSAttachmentStream : TSAttachment
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithContentType:(NSString *)contentType filename:(NSString *)filename NS_DESIGNATED_INITIALIZER; - (instancetype)initWithContentType:(NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer NS_DESIGNATED_INITIALIZER; - (instancetype)initWithPointer:(TSAttachmentPointer *)pointer NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
// Though now required, `digest` may be null for pre-existing records or from // Though now required, `digest` may be null for pre-existing records or from
// messages received from other clients // messages received from other clients
@ -32,8 +35,10 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isImage; - (BOOL)isImage;
- (BOOL)isVideo; - (BOOL)isVideo;
- (BOOL)isAudio; - (BOOL)isAudio;
- (nullable NSString *)filePath;
- (nullable NSURL *)mediaURL; - (nullable NSURL *)mediaURL;
- (nullable NSString *)filePath;
- (nullable NSData *)readDataFromFileWithError:(NSError **)error; - (nullable NSData *)readDataFromFileWithError:(NSError **)error;
- (BOOL)writeData:(NSData *)data error:(NSError **)error; - (BOOL)writeData:(NSData *)data error:(NSError **)error;

@ -6,15 +6,26 @@
#import "MIMETypeUtil.h" #import "MIMETypeUtil.h"
#import "TSAttachmentPointer.h" #import "TSAttachmentPointer.h"
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseTransaction.h> #import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface TSAttachmentStream ()
// We only want to generate the file path for this attachment once, so that
// changes in the file path generation logic don't break existing attachments.
@property (nullable, nonatomic) NSString *localRelativeFilePath;
@end
#pragma mark -
@implementation TSAttachmentStream @implementation TSAttachmentStream
- (instancetype)initWithContentType:(NSString *)contentType filename:(NSString *)filename - (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename
{ {
self = [super initWithContentType:contentType filename:filename]; self = [super initWithContentType:contentType sourceFilename:sourceFilename];
if (!self) { if (!self) {
return self; return self;
} }
@ -25,6 +36,9 @@ NS_ASSUME_NONNULL_BEGIN
// attachments which haven't been uploaded yet. // attachments which haven't been uploaded yet.
_isUploaded = NO; _isUploaded = NO;
// This instance hasn't been persisted yet.
[self ensureFilePathAndPersist:NO];
return self; return self;
} }
@ -44,6 +58,23 @@ NS_ASSUME_NONNULL_BEGIN
_isUploaded = YES; _isUploaded = YES;
self.attachmentType = pointer.attachmentType; self.attachmentType = pointer.attachmentType;
// This instance hasn't been persisted yet.
[self ensureFilePathAndPersist:NO];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
// This instance has been persisted, we need to
// update it in the database.
[self ensureFilePathAndPersist:YES];
return self; return self;
} }
@ -60,25 +91,75 @@ NS_ASSUME_NONNULL_BEGIN
} }
} }
#pragma mark - TSYapDatabaseModel overrides - (void)ensureFilePathAndPersist:(BOOL)shouldPersist
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
[super removeWithTransaction:transaction]; if (self.localRelativeFilePath) {
[self removeFile]; return;
}
NSString *attachmentsFolder = [[self class] attachmentsFolder];
NSString *filePath = [MIMETypeUtil filePathForAttachment:self.uniqueId
ofMIMEType:self.contentType
sourceFilename:self.sourceFilename
inFolder:attachmentsFolder];
if (!filePath) {
DDLogError(@"%@ Could not generate path for attachment.", self.tag);
OWSAssert(0);
return;
}
if (![filePath hasPrefix:attachmentsFolder]) {
DDLogError(@"%@ Attachment paths should all be in the attachments folder.", self.tag);
OWSAssert(0);
return;
}
NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length];
if (localRelativeFilePath.length < 1) {
DDLogError(@"%@ Empty local relative attachment paths.", self.tag);
OWSAssert(0);
return;
}
self.localRelativeFilePath = localRelativeFilePath;
OWSAssert(self.filePath);
if (shouldPersist) {
// It's not ideal to do this asynchronously, but we can create a new transaction
// within initWithCoder: which will be called from within a transaction.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(transaction);
[self saveWithTransaction:transaction];
}];
});
}
} }
#pragma mark - File Management #pragma mark - File Management
- (nullable NSData *)readDataFromFileWithError:(NSError **)error - (nullable NSData *)readDataFromFileWithError:(NSError **)error
{ {
return [NSData dataWithContentsOfFile:self.filePath options:0 error:error]; *error = nil;
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return nil;
}
return [NSData dataWithContentsOfFile:filePath options:0 error:error];
} }
- (BOOL)writeData:(NSData *)data error:(NSError **)error - (BOOL)writeData:(NSData *)data error:(NSError **)error
{ {
DDLogInfo(@"%@ Created file at %@", self.tag, self.filePath); *error = nil;
return [data writeToFile:self.filePath options:0 error:error]; NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return NO;
}
DDLogInfo(@"%@ Writing attachment to file: %@", self.tag, filePath);
return [data writeToFile:filePath options:0 error:error];
} }
+ (NSString *)attachmentsFolder + (NSString *)attachmentsFolder
@ -114,28 +195,47 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSString *)filePath - (nullable NSString *)filePath
{ {
return [MIMETypeUtil filePathForAttachment:self.uniqueId if (!self.localRelativeFilePath) {
ofMIMEType:self.contentType OWSAssert(0);
filename:self.filename return nil;
inFolder:[[self class] attachmentsFolder]]; }
return [[[self class] attachmentsFolder] stringByAppendingPathComponent:self.localRelativeFilePath];
} }
- (nullable NSURL *)mediaURL - (nullable NSURL *)mediaURL
{ {
NSString *filePath = self.filePath; NSString *_Nullable filePath = self.filePath;
return filePath ? [NSURL fileURLWithPath:filePath] : nil; if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return nil;
}
return [NSURL fileURLWithPath:filePath];
} }
- (void)removeFile - (void)removeFileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{ {
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return;
}
NSError *error; NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[self filePath] error:&error]; [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error) { if (error) {
DDLogError(@"%@ remove file errored with: %@", self.tag, error); DDLogError(@"%@ remove file errored with: %@", self.tag, error);
} }
} }
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[super removeWithTransaction:transaction];
[self removeFileWithTransaction:transaction];
}
- (BOOL)isAnimated { - (BOOL)isAnimated {
return [MIMETypeUtil isAnimated:self.contentType]; return [MIMETypeUtil isAnimated:self.contentType];
} }
@ -157,14 +257,21 @@ NS_ASSUME_NONNULL_BEGIN
if ([self isVideo] || [self isAudio]) { if ([self isVideo] || [self isAudio]) {
return [self videoThumbnail]; return [self videoThumbnail];
} else { } else {
// [self isAnimated] || [self isImage] NSURL *_Nullable mediaUrl = [self mediaURL];
return [UIImage imageWithData:[NSData dataWithContentsOfURL:[self mediaURL]]]; if (!mediaUrl) {
return nil;
}
return [UIImage imageWithData:[NSData dataWithContentsOfURL:mediaUrl]];
} }
} }
- (nullable UIImage *)videoThumbnail - (nullable UIImage *)videoThumbnail
{ {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:self.filePath] options:nil]; NSURL *_Nullable mediaUrl = [self mediaURL];
if (!mediaUrl) {
return nil;
}
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:mediaUrl options:nil];
AVAssetImageGenerator *generate = [[AVAssetImageGenerator alloc] initWithAsset:asset]; AVAssetImageGenerator *generate = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generate.appliesPreferredTrackTransform = YES; generate.appliesPreferredTrackTransform = YES;
NSError *err = NULL; NSError *err = NULL;

@ -89,7 +89,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
@property (atomic, readonly) BOOL hasSyncedTranscript; @property (atomic, readonly) BOOL hasSyncedTranscript;
@property (atomic, readonly) NSString *customMessage; @property (atomic, readonly) NSString *customMessage;
@property (atomic, readonly) NSString *mostRecentFailureText; @property (atomic, readonly) NSString *mostRecentFailureText;
// A map of attachment id-to-filename. // A map of attachment id-to-"source" filename.
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *attachmentFilenameMap; @property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *attachmentFilenameMap;
@property (atomic, readonly) TSGroupMetaMessage groupMetaMessage; @property (atomic, readonly) TSGroupMetaMessage groupMetaMessage;

@ -435,8 +435,8 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
if (!attachmentWasGroupAvatar) { if (!attachmentWasGroupAvatar) {
NSMutableArray *attachments = [NSMutableArray new]; NSMutableArray *attachments = [NSMutableArray new];
for (NSString *attachmentId in self.attachmentIds) { for (NSString *attachmentId in self.attachmentIds) {
NSString *filename = self.attachmentFilenameMap[attachmentId]; NSString *sourceFilename = self.attachmentFilenameMap[attachmentId];
[attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:filename]]; [attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:sourceFilename]];
} }
[builder setAttachmentsArray:attachments]; [builder setAttachmentsArray:attachments];
} }

@ -71,7 +71,7 @@ NS_SWIFT_NAME(MessageSender)
*/ */
- (void)sendAttachmentData:(NSData *)attachmentData - (void)sendAttachmentData:(NSData *)attachmentData
contentType:(NSString *)contentType contentType:(NSString *)contentType
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
inMessage:(TSOutgoingMessage *)outgoingMessage inMessage:(TSOutgoingMessage *)outgoingMessage
success:(void (^)())successHandler success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler; failure:(void (^)(NSError *error))failureHandler;

@ -493,7 +493,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self sendAttachmentData:attachmentData [self sendAttachmentData:attachmentData
contentType:contentType contentType:contentType
filename:nil sourceFilename:nil
inMessage:message inMessage:message
success:successWithDeleteHandler success:successWithDeleteHandler
failure:failureWithDeleteHandler]; failure:failureWithDeleteHandler];
@ -501,7 +501,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
- (void)sendAttachmentData:(NSData *)data - (void)sendAttachmentData:(NSData *)data
contentType:(NSString *)contentType contentType:(NSString *)contentType
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
inMessage:(TSOutgoingMessage *)message inMessage:(TSOutgoingMessage *)message
success:(void (^)())successHandler success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler failure:(void (^)(NSError *error))failureHandler
@ -515,7 +515,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
dispatch_async([OWSDispatch attachmentsQueue], ^{ dispatch_async([OWSDispatch attachmentsQueue], ^{
TSAttachmentStream *attachmentStream = TSAttachmentStream *attachmentStream =
[[TSAttachmentStream alloc] initWithContentType:contentType filename:filename]; [[TSAttachmentStream alloc] initWithContentType:contentType sourceFilename:sourceFilename];
if (message.isVoiceMessage) { if (message.isVoiceMessage) {
attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage; attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage;
} }
@ -529,8 +529,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[attachmentStream save]; [attachmentStream save];
[message.attachmentIds addObject:attachmentStream.uniqueId]; [message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) { if (sourceFilename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename; message.attachmentFilenameMap[attachmentStream.uniqueId] = sourceFilename;
} }
[message save]; [message save];

@ -758,7 +758,7 @@ NS_ASSUME_NONNULL_BEGIN
if (gThread.groupModel.groupImage) { if (gThread.groupModel.groupImage) {
[self.messageSender sendAttachmentData:UIImagePNGRepresentation(gThread.groupModel.groupImage) [self.messageSender sendAttachmentData:UIImagePNGRepresentation(gThread.groupModel.groupImage)
contentType:OWSMimeTypeImagePng contentType:OWSMimeTypeImagePng
filename:nil sourceFilename:nil
inMessage:message inMessage:message
success:^{ success:^{
DDLogDebug(@"%@ Successfully sent group update with avatar", self.tag); DDLogDebug(@"%@ Successfully sent group update with avatar", self.tag);

@ -34,7 +34,7 @@ extern NSString *const OWSMimeTypeUnknownForTests;
// filename is optional and should not be trusted. // filename is optional and should not be trusted.
+ (nullable NSString *)filePathForAttachment:(NSString *)uniqueId + (nullable NSString *)filePathForAttachment:(NSString *)uniqueId
ofMIMEType:(NSString *)contentType ofMIMEType:(NSString *)contentType
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
inFolder:(NSString *)folder; inFolder:(NSString *)folder;
+ (NSSet<NSString *> *)supportedVideoUTITypes; + (NSSet<NSString *> *)supportedVideoUTITypes;

@ -267,14 +267,14 @@ NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype";
+ (nullable NSString *)filePathForAttachment:(NSString *)uniqueId + (nullable NSString *)filePathForAttachment:(NSString *)uniqueId
ofMIMEType:(NSString *)contentType ofMIMEType:(NSString *)contentType
filename:(nullable NSString *)filename sourceFilename:(nullable NSString *)sourceFilename
inFolder:(NSString *)folder inFolder:(NSString *)folder
{ {
NSString *kDefaultFileExtension = @"bin"; NSString *kDefaultFileExtension = @"bin";
if (filename.length > 0) { if (sourceFilename.length > 0) {
NSString *normalizedFilename = NSString *normalizedFilename =
[filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [sourceFilename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
// Ensure that the filename is a valid filesystem name, // Ensure that the filename is a valid filesystem name,
// replacing invalid characters with an underscore. // replacing invalid characters with an underscore.
for (NSCharacterSet *invalidCharacterSet in @[ for (NSCharacterSet *invalidCharacterSet in @[

Loading…
Cancel
Save