Rework preservation of attachment filenames.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-09-08 15:16:24 -04:00
parent 0746b1300d
commit c21a7673c8
8 changed files with 92 additions and 72 deletions

View File

@ -366,8 +366,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
}
id<DataSource> _Nullable dataSource = [DataSourcePath dataSourceWithURL:url];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
if (!attachment) {
DDLogError(@"Application opened with URL with invalid content: %@", url);
[OWSAlerts showAlertWithTitle:

View File

@ -71,17 +71,15 @@ class SignalAttachment: NSObject {
public var dataUrl: URL? {
return dataSource.dataUrl()
}
public var sourceFilename: String? {
return dataSource.sourceFilename()
}
// Attachment types are identified using UTIs.
//
// See: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
let dataUTI: String
// An optional field that indicates the filename, if known, for this attachment.
//
// TODO: Try to eliminate this property.
let filename: String?
var error: SignalAttachmentError? {
didSet {
AssertIsOnMainThread()
@ -115,10 +113,9 @@ class SignalAttachment: NSObject {
// This method should not be called directly; use the factory
// methods instead.
internal required init(dataSource: DataSource, dataUTI: String, filename: String?) {
internal required init(dataSource: DataSource, dataUTI: String) {
self.dataSource = dataSource
self.dataUTI = dataUTI
self.filename = filename
super.init()
}
@ -165,7 +162,7 @@ class SignalAttachment: NSObject {
return "audio/aac"
}
if let filename = filename {
if let filename = sourceFilename {
let fileExtension = (filename as NSString).pathExtension
if fileExtension.characters.count > 0 {
if let mimeType = MIMETypeUtil.mimeType(forFileExtension:fileExtension) {
@ -192,7 +189,7 @@ class SignalAttachment: NSObject {
// Use the filename if known. If not, e.g. if the attachment was copy/pasted, we'll generate a filename
// like: "signal-2017-04-24-095918.zip"
var filenameOrDefault: String {
if let filename = filename {
if let filename = sourceFilename {
return filename
} else {
let kDefaultAttachmentName = "signal"
@ -213,7 +210,7 @@ class SignalAttachment: NSObject {
// Returns the file extension for this attachment or nil if no file extension
// can be identified.
var fileExtension: String? {
if let filename = filename {
if let filename = sourceFilename {
let fileExtension = (filename as NSString).pathExtension
if fileExtension.characters.count > 0 {
return fileExtension
@ -378,7 +375,7 @@ class SignalAttachment: NSObject {
return nil
}
let dataSource = DataSourceValue.dataSource(with:data, utiType: dataUTI)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI)
}
}
for dataUTI in videoUTISet {
@ -388,7 +385,7 @@ class SignalAttachment: NSObject {
return nil
}
let dataSource = DataSourceValue.dataSource(with:data, utiType: dataUTI)
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI)
}
}
for dataUTI in audioUTISet {
@ -398,7 +395,7 @@ class SignalAttachment: NSObject {
return nil
}
let dataSource = DataSourceValue.dataSource(with:data, utiType: dataUTI)
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI)
}
}
@ -408,7 +405,7 @@ class SignalAttachment: NSObject {
return nil
}
let dataSource = DataSourceValue.dataSource(with:data, utiType: dataUTI)
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI)
}
// This method should only be called for dataUTIs that
@ -436,17 +433,17 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func imageAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
private class func imageAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(dataSource != nil)
guard let dataSource = dataSource else {
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI)
guard inputImageUTISet.contains(dataUTI) else {
attachment.error = .invalidFileFormat
@ -480,7 +477,7 @@ class SignalAttachment: NSObject {
}
Logger.verbose("\(TAG) Compressing attachment as image/jpeg")
return compressImageAsJPEG(image : image, attachment : attachment, filename:filename)
return compressImageAsJPEG(image : image, attachment : attachment, filename:dataSource.sourceFilename())
}
}
@ -520,13 +517,17 @@ class SignalAttachment: NSObject {
assert(dataUTI.characters.count > 0)
guard let image = image else {
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename)
let dataSource = DataSourceValue.emptyDataSource()
dataSource.setSourceFilename(filename)
let attachment = SignalAttachment(dataSource:dataSource, dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
// Make a placeholder attachment on which to hang errors if necessary.
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename)
let dataSource = DataSourceValue.emptyDataSource()
dataSource.setSourceFilename(filename)
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI)
attachment.image = image
Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg")
@ -555,9 +556,10 @@ class SignalAttachment: NSObject {
attachment.error = .couldNotConvertToJpeg
return attachment
}
dataSource.setSourceFilename(filename)
if UInt(jpgImageData.count) <= kMaxFileSizeImage {
let recompressedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: kUTTypeJPEG as String, filename: filename)
let recompressedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: kUTTypeJPEG as String)
recompressedAttachment.image = dstImage
return recompressedAttachment
}
@ -627,12 +629,11 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func videoAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
private class func videoAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : videoUTISet,
maxFileSize : kMaxFileSizeVideo,
filename : filename)
maxFileSize : kMaxFileSizeVideo)
}
// MARK: Audio Attachments
@ -641,12 +642,11 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func audioAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
private class func audioAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : audioUTISet,
maxFileSize : kMaxFileSizeAudio,
filename : filename)
maxFileSize : kMaxFileSizeAudio)
}
// MARK: Oversize Text Attachments
@ -660,8 +660,7 @@ class SignalAttachment: NSObject {
return newAttachment(dataSource : dataSource,
dataUTI : kOversizeTextAttachmentUTI,
validUTISet : nil,
maxFileSize : kMaxFileSizeGeneric,
filename : nil)
maxFileSize : kMaxFileSizeGeneric)
}
// MARK: Generic Attachments
@ -670,18 +669,17 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func genericAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
private class func genericAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : nil,
maxFileSize : kMaxFileSizeGeneric,
filename : filename)
maxFileSize : kMaxFileSizeGeneric)
}
// MARK: Voice Messages
public class func voiceMessageAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
let attachment = audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
public class func voiceMessageAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
let attachment = audioAttachment(dataSource : dataSource, dataUTI : dataUTI)
attachment.isVoiceMessage = true
return attachment
}
@ -692,22 +690,21 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func attachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
public class func attachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
if inputImageUTISet.contains(dataUTI) {
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI)
} else if videoUTISet.contains(dataUTI) {
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI)
} else if audioUTISet.contains(dataUTI) {
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI)
} else {
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI)
}
}
public class func empty() -> SignalAttachment {
return SignalAttachment.attachment(dataSource : DataSourceValue.emptyDataSource(),
dataUTI: kUTTypeContent as String,
filename:nil)
dataUTI: kUTTypeContent as String)
}
// MARK: Helper Methods
@ -715,18 +712,17 @@ class SignalAttachment: NSObject {
private class func newAttachment(dataSource: DataSource?,
dataUTI: String,
validUTISet: Set<String>?,
maxFileSize: UInt,
filename: String?) -> SignalAttachment {
maxFileSize: UInt) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(dataSource != nil)
guard let dataSource = dataSource else {
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI)
if let validUTISet = validUTISet {
guard validUTISet.contains(dataUTI) else {

View File

@ -283,10 +283,10 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
}
private func formattedFileName() -> String? {
guard let rawFilename = attachment.filename else {
guard let sourceFilename = attachment.sourceFilename else {
return nil
}
let filename = rawFilename.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let filename = sourceFilename.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
guard filename.characters.count > 0 else {
return nil
}

View File

@ -1675,7 +1675,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
if ([text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
id<DataSource> _Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:text];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI filename:nil];
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
message =
[ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread messageSender:self.messageSender];
} else {
@ -3207,8 +3207,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
OWSAssert(type);
OWSAssert(filename);
id<DataSource> _Nullable dataSource = [DataSourcePath dataSourceWithURL:url];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:type filename:filename];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:type];
[self tryToSendAttachmentIfApproved:attachment];
}
@ -3226,13 +3226,17 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
picker.mediaTypes = @[ (__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie ];
picker.allowsEditing = NO;
picker.delegate = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
});
}];
}
- (void)chooseFromLibrary
{
OWSAssert([NSThread isMainThread]);
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
DDLogError(@"PhotoLibrary ImagePicker source not available");
return;
@ -3242,9 +3246,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
picker.mediaTypes = @[ (__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie ];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
});
[self presentViewController:picker animated:YES completion:[UIUtil modalCompletionBlock]];
}
/*
@ -3376,8 +3379,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
id<DataSource> _Nullable dataSource =
[DataSourceValue dataSourceWithData:imageData utiType:dataUTI];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:dataUTI filename:filename];
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:dataUTI];
[self dismissViewControllerAnimated:YES
completion:^{
OWSAssert([NSThread isMainThread]);
@ -3424,8 +3428,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
- (NSURL *)videoTempFolder
{
NSString *temporaryDirectory = NSTemporaryDirectory();
// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
// NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *videoDirPath = [temporaryDirectory stringByAppendingPathComponent:@"videos"];
if (![[NSFileManager defaultManager] fileExistsAtPath:videoDirPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:videoDirPath
@ -3436,10 +3438,24 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
return [NSURL fileURLWithPath:videoDirPath];
}
- (NSUInteger)sizeOfFileURL:(NSURL *)movieURL
{
NSError *error;
NSDictionary<NSFileAttributeKey, id> *_Nullable attributes =
[[NSFileManager defaultManager] attributesOfItemAtPath:movieURL.path error:&error];
OWSAssert(attributes);
OWSAssert(!error);
uint64_t fileSize = [attributes fileSize];
return (NSUInteger)fileSize;
}
- (void)sendQualityAdjustedAttachmentForVideo:(NSURL *)movieURL
filename:(NSString *)filename
skipApprovalDialog:(BOOL)skipApprovalDialog
{
OWSAssert([NSThread isMainThread]);
DDLogError(@"movieURL: %@ %zd", movieURL, [self sizeOfFileURL:movieURL]);
AVAsset *video = [AVAsset assetWithURL:movieURL];
AVAssetExportSession *exportSession =
[AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality];
@ -3449,11 +3465,12 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
URLByAppendingPathComponent:[[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"mp4"]];
exportSession.outputURL = compressedVideoUrl;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
DDLogError(@"compressedVideoUrl: %@ %zd", compressedVideoUrl, [self sizeOfFileURL:compressedVideoUrl]);
dispatch_async(dispatch_get_main_queue(), ^{
id<DataSource> _Nullable dataSource = [DataSourcePath dataSourceWithURL:compressedVideoUrl];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypeMPEG4
filename:filename];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:(NSString *)kUTTypeMPEG4];
if (!attachment || [attachment hasError]) {
DDLogError(@"%@ %s Invalid attachment: %@.",
self.tag,
@ -3832,10 +3849,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
NSString *filename = [NSLocalizedString(@"VOICE_MESSAGE_FILE_NAME", @"Filename for voice messages.")
stringByAppendingPathExtension:@"m4a"];
SignalAttachment *attachment = [SignalAttachment voiceMessageAttachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypeMPEG4Audio
filename:filename];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment voiceMessageAttachmentWithDataSource:dataSource dataUTI:(NSString *)kUTTypeMPEG4Audio];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.tag,

View File

@ -328,11 +328,11 @@ NS_ASSUME_NONNULL_BEGIN
NSString *filename = [filePath lastPathComponent];
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
id<DataSource> _Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
OWSAssert(attachment);
if ([attachment hasError]) {
DDLogError(@"attachment[%@]: %@", [attachment filename], [attachment errorName]);
DDLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
@ -589,7 +589,7 @@ NS_ASSUME_NONNULL_BEGIN
id<DataSource> _Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI filename:nil];
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender];
}
@ -615,7 +615,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSMessageSender *messageSender = [Environment getCurrent].messageSender;
id<DataSource> _Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:length] utiType:uti];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti filename:nil];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender ignoreErrors:YES];
}
+ (OWSSignalServiceProtosEnvelope *)createEnvelopeForThread:(TSThread *)thread

View File

@ -141,8 +141,9 @@ NS_ASSUME_NONNULL_BEGIN
//
// TODO: If we reuse this VC, for example to offer a "forward attachment to other thread",
// feature, this assumption would no longer apply.
OWSAssert(self.attachment) NSString *filename =
[self.attachment.filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
OWSAssert(self.attachment);
NSString *filename =
[self.attachment.sourceFilename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
OWSAssert(filename.length > 0);
const NSUInteger kMaxFilenameLength = 20;
if (filename.length > kMaxFilenameLength) {

View File

@ -45,6 +45,9 @@ NS_ASSUME_NONNULL_BEGIN
// Returns YES on success.
- (BOOL)writeToPath:(NSString *)dstFilePath;
- (nullable NSString *)sourceFilename;
- (void)setSourceFilename:(NSString *_Nullable)sourceFilename;
@end
#pragma mark -

View File

@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN
// This property is lazy-populated.
@property (nonatomic) NSString *cachedFilePath;
@property (nonatomic, nullable) NSString *sourceFilename;
@end
#pragma mark -
@ -153,6 +155,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) NSData *cachedData;
@property (nonatomic) NSNumber *cachedDataLength;
@property (nonatomic, nullable) NSString *sourceFilename;
@end
#pragma mark -