From 69816cdf0e62544ddcb0c4753cd2042916d28e57 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 8 Sep 2017 11:41:40 -0400 Subject: [PATCH] Convert DataSource to Objective-C. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 - Signal/src/AppDelegate.m | 2 +- Signal/src/Models/DataSource.swift | 217 --------- Signal/src/Models/SignalAttachment.swift | 47 +- Signal/src/Signal-Bridging-Header.h | 1 + .../ConversationViewController.m | 11 +- .../ViewControllers/DebugUI/DebugUIMessages.m | 7 +- SignalServiceKit/src/Util/DataSource.h | 75 +++ SignalServiceKit/src/Util/DataSource.m | 436 ++++++++++++++++++ 9 files changed, 549 insertions(+), 251 deletions(-) delete mode 100644 Signal/src/Models/DataSource.swift create mode 100755 SignalServiceKit/src/Util/DataSource.h create mode 100755 SignalServiceKit/src/Util/DataSource.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 2b629191d..fe83aa68a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -89,7 +89,6 @@ 34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; }; 34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; }; 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; }; - 34D9134A1F62D4A500722898 /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D913481F62D4A500722898 /* DataSource.swift */; }; 34D9134B1F62D4A500722898 /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D913491F62D4A500722898 /* SignalAttachment.swift */; }; 34D99C8C1F27B13B00D284D6 /* OWSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C8B1F27B13B00D284D6 /* OWSViewController.m */; }; 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; }; @@ -551,7 +550,6 @@ 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DebugUITableViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 34D8C0291ED3685800188D7C /* DebugUIContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIContacts.h; sourceTree = ""; }; 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = ""; }; - 34D913481F62D4A500722898 /* DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = ""; }; 34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = ""; }; 34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = ""; }; 34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = ""; }; @@ -1231,7 +1229,6 @@ children = ( 45CD81EE1DC030E7004C9430 /* AccountManager.swift */, 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */, - 34D913481F62D4A500722898 /* DataSource.swift */, 45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */, 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */, 45C681B51D305A580050903A /* OWSCall.h */, @@ -2325,7 +2322,6 @@ 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, FCFA64B41A24F3880007FB87 /* UIColor+OWS.m in Sources */, 4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */, - 34D9134A1F62D4A500722898 /* DataSource.swift in Sources */, 450573FE1E78A06D00615BB4 /* OWS103EnableVideoCalling.m in Sources */, 34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */, 34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index f3f33df1c..7d6cefe54 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -365,7 +365,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; return NO; } - DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:url]; + id _Nullable dataSource = [DataSourcePath dataSourceWithURL:url]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename]; if (!attachment) { diff --git a/Signal/src/Models/DataSource.swift b/Signal/src/Models/DataSource.swift deleted file mode 100644 index 2dbde4137..000000000 --- a/Signal/src/Models/DataSource.swift +++ /dev/null @@ -1,217 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc -protocol DataSource { - // This method should not be called unless necessary as it - // be expensive. - func data() -> Data - - func dataUrl(fileExtension: String) -> URL? - func dataPath(fileExtension: String) -> String? - func dataPathIfOnDisk() -> String? - func dataLength() -> Int -} - -@objc -class DataSourceValue: NSObject, DataSource { - static let TAG = "[DataSourceValue]" - - private let value: Data - - private var path: String? - - // MARK: Constructor - - internal required init(_ value: Data) { - self.value = value - super.init() - } - - func data() -> Data { - return value - } - - func dataUrl(fileExtension: String) -> URL? { - guard let path = dataPath(fileExtension:fileExtension) else { - return nil - } - return URL(fileURLWithPath: path) - } - - func dataPath(fileExtension: String) -> String? { - if let path = path { - return path - } - - let directory = NSTemporaryDirectory() - let fileName = NSUUID().uuidString + "." + fileExtension - let filePath = (directory as NSString).appendingPathComponent(fileName) - do { - try value.write(to: URL(fileURLWithPath:filePath)) - path = filePath - } catch { - owsFail("\(DataSourceValue.TAG) Could not write data to disk: \(fileExtension)") - } - return filePath - } - - func dataPathIfOnDisk() -> String? { - if let path = path { - return path - } - return nil - } - - func dataLength() -> Int { - return value.count - } - - class func empty() -> DataSource { - return DataSourceValue(Data()) - } -} - -@objc -class DataSourcePath: NSObject, DataSource { - static let TAG = "[DataSourcePath]" - - private let path: String - - private var cachedData: Data? - - private var cachedLength: Int? - - // MARK: Constructor - - internal required init(_ path: String) { - self.path = path - super.init() - } - - func data() -> Data { - if let cachedData = cachedData { - return cachedData - } - Logger.error("\(DataSourcePath.TAG) reading data: \(path)") - do { - try cachedData = NSData(contentsOfFile:path) as Data - } catch { - owsFail("\(DataSourcePath.TAG) Could not read data from disk: \(path)") - cachedData = Data() - } - return cachedData! - } - - func dataUrl(fileExtension: String) -> URL? { - return URL(fileURLWithPath: path) - } - - func dataPath(fileExtension: String) -> String? { - return path - } - - func dataPathIfOnDisk() -> String? { - return path - } - - func dataLength() -> Int { - if let cachedLength = cachedLength { - return cachedLength - } - - do { - let fileAttributes = try FileManager.default.attributesOfItem(atPath: path) - let fileSize = fileAttributes[FileAttributeKey.size] as! UInt64 - cachedLength = Int(fileSize) - } catch { - owsFail("\(DataSourcePath.TAG) Could not read data length from disk: \(path)") - cachedLength = 0 - } - - return cachedLength! - } -} - -@objc -class DataSourceUrl: NSObject, DataSource { - static let TAG = "[DataSourceUrl]" - - private let url: URL - - private var cachedData: Data? - - private var cachedLength: Int? - - // MARK: Constructor - - internal required init(_ url: URL) { - if !url.isFileURL { - owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") - } - self.url = url - super.init() - } - - func data() -> Data { - if let cachedData = cachedData { - return cachedData - } - guard url.isFileURL else { - owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") - return Data() - } - Logger.error("\(DataSourceUrl.TAG) reading data: \(url)") - do { - try cachedData = Data(contentsOf:url) - } catch { - owsFail("\(DataSourceUrl.TAG) Could not read data from disk: \(url)") - cachedData = Data() - } - return cachedData! - } - - func dataUrl(fileExtension: String) -> URL? { - return url - } - - func dataPath(fileExtension: String) -> String? { - guard url.isFileURL else { - owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") - return nil - } - return url.path - } - - func dataPathIfOnDisk() -> String? { - guard url.isFileURL else { - owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") - return nil - } - return url.path - } - - func dataLength() -> Int { - if let cachedLength = cachedLength { - return cachedLength - } - guard url.isFileURL else { - owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") - return 0 - } - - do { - let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path) - let fileSize = fileAttributes[FileAttributeKey.size] as! UInt64 - cachedLength = Int(fileSize) - } catch { - owsFail("\(DataSourceUrl.TAG) Could not read data length from disk: \(url)") - cachedLength = 0 - } - - return cachedLength! - } -} diff --git a/Signal/src/Models/SignalAttachment.swift b/Signal/src/Models/SignalAttachment.swift index 25ecc80aa..89efa0b09 100644 --- a/Signal/src/Models/SignalAttachment.swift +++ b/Signal/src/Models/SignalAttachment.swift @@ -66,11 +66,11 @@ class SignalAttachment: NSObject { public var data: Data { return dataSource.data() } - public var dataLength: Int { + public var dataLength: UInt { return dataSource.dataLength() } public var dataUrl: URL? { - return dataSource.dataUrl(fileExtension:fileExtensionNonNil) + return dataSource.dataUrl(fileExtensionNonNil) } // Attachment types are identified using UTIs. @@ -107,11 +107,11 @@ class SignalAttachment: NSObject { * * https://github.com/WhisperSystems/Signal-Android/blob/master/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java */ - static let kMaxFileSizeAnimatedImage = 25 * 1024 * 1024 - static let kMaxFileSizeImage = 6 * 1024 * 1024 - static let kMaxFileSizeVideo = 100 * 1024 * 1024 - static let kMaxFileSizeAudio = 100 * 1024 * 1024 - static let kMaxFileSizeGeneric = 100 * 1024 * 1024 + static let kMaxFileSizeAnimatedImage = UInt(25 * 1024 * 1024) + static let kMaxFileSizeImage = UInt(6 * 1024 * 1024) + static let kMaxFileSizeVideo = UInt(100 * 1024 * 1024) + static let kMaxFileSizeAudio = UInt(100 * 1024 * 1024) + static let kMaxFileSizeGeneric = UInt(100 * 1024 * 1024) // MARK: Constructor @@ -379,7 +379,7 @@ class SignalAttachment: NSObject { owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)") return nil } - let dataSource = DataSourceValue(data) + let dataSource = DataSourceValue.dataSource(with:data) return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil) } } @@ -389,7 +389,7 @@ class SignalAttachment: NSObject { owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)") return nil } - let dataSource = DataSourceValue(data) + let dataSource = DataSourceValue.dataSource(with:data) return videoAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil) } } @@ -399,7 +399,7 @@ class SignalAttachment: NSObject { owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)") return nil } - let dataSource = DataSourceValue(data) + let dataSource = DataSourceValue.dataSource(with:data) return audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil) } } @@ -409,7 +409,7 @@ class SignalAttachment: NSObject { owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)") return nil } - let dataSource = DataSourceValue(data) + let dataSource = DataSourceValue.dataSource(with:data) return genericAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil) } @@ -443,7 +443,7 @@ class SignalAttachment: NSObject { assert(dataSource != nil) guard let dataSource = dataSource else { - let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename) + let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename) attachment.error = .missingData return attachment } @@ -522,13 +522,13 @@ class SignalAttachment: NSObject { assert(dataUTI.characters.count > 0) guard let image = image else { - let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename) + let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename) attachment.error = .missingData return attachment } // Make a placeholder attachment on which to hang errors if necessary. - let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename) + let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename) attachment.image = image Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg") @@ -553,8 +553,13 @@ class SignalAttachment: NSObject { return attachment } - if jpgImageData.count <= kMaxFileSizeImage { - let recompressedAttachment = SignalAttachment(dataSource : DataSourceValue(jpgImageData), dataUTI: kUTTypeJPEG as String, filename: filename) + guard let dataSource = DataSourceValue.dataSource(with:jpgImageData) else { + attachment.error = .couldNotConvertToJpeg + return attachment + } + + if UInt(jpgImageData.count) <= kMaxFileSizeImage { + let recompressedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: kUTTypeJPEG as String, filename: filename) recompressedAttachment.image = dstImage return recompressedAttachment } @@ -653,9 +658,9 @@ class SignalAttachment: NSObject { // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. private class func oversizeTextAttachment(text: String?) -> SignalAttachment { - var dataSource = DataSourceValue.empty() + var dataSource: DataSource? = DataSourceValue.emptyDataSource() if let data = text?.data(using: .utf8) { - dataSource = DataSourceValue(data) + dataSource = DataSourceValue.dataSource(with:data) } return newAttachment(dataSource : dataSource, dataUTI : kOversizeTextAttachmentUTI, @@ -705,7 +710,7 @@ class SignalAttachment: NSObject { } public class func empty() -> SignalAttachment { - return SignalAttachment.attachment(dataSource : DataSourceValue.empty(), + return SignalAttachment.attachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: kUTTypeContent as String, filename:nil) } @@ -715,13 +720,13 @@ class SignalAttachment: NSObject { private class func newAttachment(dataSource: DataSource?, dataUTI: String, validUTISet: Set?, - maxFileSize: Int, + maxFileSize: UInt, filename: String?) -> SignalAttachment { assert(dataUTI.characters.count > 0) assert(dataSource != nil) guard let dataSource = dataSource else { - let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename) + let attachment = SignalAttachment(dataSource : DataSourceValue.emptyDataSource(), dataUTI: dataUTI, filename: filename) attachment.error = .missingData return attachment } diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 5c257853f..130670cf0 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -49,6 +49,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index ebea19e5f..3ac5464f1 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1673,7 +1673,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; TSOutgoingMessage *message; if ([text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) { - DataSourceValue *dataSource = [[DataSourceValue alloc] init:[text dataUsingEncoding:NSUTF8StringEncoding]]; + id _Nullable dataSource = + [DataSourceValue dataSourceWithData:[text dataUsingEncoding:NSUTF8StringEncoding]]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:SignalAttachment.kOversizeTextAttachmentUTI @@ -3208,7 +3209,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { OWSAssert(type); OWSAssert(filename); - DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:url]; + id _Nullable dataSource = [DataSourcePath dataSourceWithURL:url]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:type filename:filename]; [self tryToSendAttachmentIfApproved:attachment]; @@ -3376,7 +3377,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { } OWSAssert([NSThread isMainThread]); - DataSourceValue *dataSource = [[DataSourceValue alloc] init:imageData]; + id _Nullable dataSource = [DataSourceValue dataSourceWithData:imageData]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:dataUTI filename:filename]; [self dismissViewControllerAnimated:YES @@ -3451,7 +3452,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { exportSession.outputURL = compressedVideoUrl; [exportSession exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ - DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:compressedVideoUrl]; + id _Nullable dataSource = [DataSourcePath dataSourceWithURL:compressedVideoUrl]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:(NSString *)kUTTypeMPEG4 filename:filename]; @@ -3828,7 +3829,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { return; } - DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:self.audioRecorder.url]; + id _Nullable dataSource = [DataSourcePath dataSourceWithURL:self.audioRecorder.url]; self.audioRecorder = nil; NSString *filename = [NSLocalizedString(@"VOICE_MESSAGE_FILE_NAME", @"Filename for voice messages.") diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index c2a17a830..badcd8a4b 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -328,7 +328,7 @@ NS_ASSUME_NONNULL_BEGIN OWSMessageSender *messageSender = [Environment getCurrent].messageSender; NSString *filename = [filePath lastPathComponent]; NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension]; - DataSourcePath *dataSource = [[DataSourcePath alloc] init:filePath]; + id _Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename]; OWSAssert(attachment); @@ -588,7 +588,8 @@ NS_ASSUME_NONNULL_BEGIN @"lorem, in rhoncus nisi."]; } - DataSourceValue *dataSource = [[DataSourceValue alloc] init:[message dataUsingEncoding:NSUTF8StringEncoding]]; + id _Nullable dataSource = + [DataSourceValue dataSourceWithData:[message dataUsingEncoding:NSUTF8StringEncoding]]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:SignalAttachment.kOversizeTextAttachmentUTI @@ -616,7 +617,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti length:(NSUInteger)length { OWSMessageSender *messageSender = [Environment getCurrent].messageSender; - DataSourceValue *dataSource = [[DataSourceValue alloc] init:[self createRandomNSDataOfSize:length]]; + id _Nullable dataSource = [DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:length]]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti filename:nil]; [ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender ignoreErrors:YES]; } diff --git a/SignalServiceKit/src/Util/DataSource.h b/SignalServiceKit/src/Util/DataSource.h new file mode 100755 index 000000000..f5f1e2eb0 --- /dev/null +++ b/SignalServiceKit/src/Util/DataSource.h @@ -0,0 +1,75 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +// A protocol that abstracts away a source of NSData +// and allows us to: +// +// * Lazy-load if possible. +// * Avoid duplicate reads & writes. +@protocol DataSource + +// Should not be called unless necessary as it can involve an expensive read. +- (NSData *)data; + +// The URL for the data. Should always be a File URL. +// +// Should not be called unless necessary as it can involve an expensive write. +// +// Will only return nil in the error case. +// +// TODO: Try to remove the parameter. +- (nullable NSURL *)dataUrl:(NSString *)fileExtension; + +// The file path for the data. +// +// Should not be called unless necessary as it can involve an expensive write. +// +// Will only return nil in the error case. +// +// TODO: Try to remove the parameter. +- (nullable NSString *)dataPath:(NSString *)fileExtension; + +// The file path for the data, if it already exists on disk. +// +// This method is safe to call as it will not do any expensive reads or writes. +// +// May return nil if the data does not reside on disk. +- (nullable NSString *)dataPathIfOnDisk; + +// Will return zero in the error case. +- (NSUInteger)dataLength; + +@end + +#pragma mark - + +@interface DataSourceValue : NSObject + ++ (nullable id)dataSourceWithData:(NSData *)data; + ++ (id)emptyDataSource; + +@end + +#pragma mark - + +@interface DataSourcePath : NSObject + ++ (nullable id)dataSourceWithURL:(NSURL *)fileUrl; + ++ (nullable id)dataSourceWithFilePath:(NSString *)filePath; + +@end + +//#pragma mark - +// +//@interface DataSourceURL : NSObject +// +//+ (id)dataSourceWithURL:(NSURL *)fileUrl; +// +//@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m new file mode 100755 index 000000000..28bc684cc --- /dev/null +++ b/SignalServiceKit/src/Util/DataSource.m @@ -0,0 +1,436 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "DataSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DataSourceValue () + +@property (nonatomic) NSData *dataValue; + +// This property is lazy-populated. +@property (nonatomic) NSString *cachedFilePath; + +@end + +#pragma mark - + +@implementation DataSourceValue + ++ (nullable id)dataSourceWithData:(NSData *)data +{ + OWSAssert(data); + if (!data) { + return nil; + } + + DataSourceValue *instance = [DataSourceValue new]; + instance.dataValue = data; + return instance; +} + ++ (id)emptyDataSource +{ + return [self dataSourceWithData:[NSData new]]; +} + +- (NSData *)data +{ + OWSAssert(self.dataValue); + + return self.dataValue; +} + +- (nullable NSURL *)dataUrl:(NSString *)fileExtension +{ + NSString *_Nullable path = [self dataPath:fileExtension]; + return (path ? [NSURL fileURLWithPath:path] : nil); +} + +- (nullable NSString *)dataPath:(NSString *)fileExtension +{ + OWSAssert(self.dataValue); + + @synchronized(self) + { + if (!self.cachedFilePath) { + NSString *dirPath = NSTemporaryDirectory(); + NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:fileExtension]; + NSString *filePath = [dirPath stringByAppendingPathComponent:fileName]; + if ([self.dataValue writeToFile:fileName atomically:YES]) { + self.cachedFilePath = filePath; + } else { + OWSFail(@"%@ Could not write data to disk: %@", self.tag, fileExtension); + } + } + + return self.cachedFilePath; + } +} + +- (nullable NSString *)dataPathIfOnDisk +{ + return self.cachedFilePath; +} + +- (NSUInteger)dataLength +{ + OWSAssert(self.dataValue); + + return self.dataValue.length; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +#pragma mark - + +@interface DataSourcePath () + +@property (nonatomic) NSString *filePath; + +// These properties are lazy-populated. +@property (nonatomic) NSData *cachedData; +@property (nonatomic) NSNumber *cachedDataLength; + +@end + +#pragma mark - + +@implementation DataSourcePath + ++ (nullable id)dataSourceWithURL:(NSURL *)fileUrl; +{ + OWSAssert(fileUrl); + + if (!fileUrl || ![fileUrl isFileURL]) { + return nil; + } + DataSourcePath *instance = [DataSourcePath new]; + instance.filePath = fileUrl.path; + return instance; +} + ++ (nullable id)dataSourceWithFilePath:(NSString *)filePath; +{ + OWSAssert(filePath); + + if (!filePath) { + return nil; + } + + DataSourcePath *instance = [DataSourcePath new]; + instance.filePath = filePath; + return instance; +} + +- (NSData *)data +{ + OWSAssert(self.filePath); + + @synchronized(self) + { + if (!self.cachedData) { + self.cachedData = [NSData dataWithContentsOfFile:self.filePath]; + } + if (!self.cachedData) { + OWSFail(@"%@ Could not read data from disk: %@", self.tag, self.filePath); + self.cachedData = [NSData new]; + } + return self.cachedData; + } +} + +- (nullable NSURL *)dataUrl:(NSString *)fileExtension +{ + OWSAssert(self.filePath); + + return [NSURL fileURLWithPath:self.filePath]; +} + +- (nullable NSString *)dataPath:(NSString *)fileExtension +{ + OWSAssert(self.filePath); + + return self.filePath; +} + +- (nullable NSString *)dataPathIfOnDisk +{ + OWSAssert(self.filePath); + + return self.filePath; +} + +- (NSUInteger)dataLength +{ + OWSAssert(self.filePath); + + @synchronized(self) + { + if (!self.cachedDataLength) { + NSError *error; + NSDictionary *_Nullable attributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error]; + if (!attributes || error) { + OWSFail(@"%@ Could not read data length from disk: %@", self.tag, self.filePath); + self.cachedDataLength = @(0); + } else { + uint64_t fileSize = [attributes fileSize]; + self.cachedDataLength = @(fileSize); + } + } + return [self.cachedDataLength unsignedIntegerValue]; + } +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +//#pragma mark - +// +//@interface DataSourceURL () +// +//@property (nonatomic) NSURL *fileUrl; +// +//// These properties are lazy-populated. +//@property (nonatomic) NSData *cachedData; +//@property (nonatomic) NSNumber *cachedDataLength; +// +//@end +// +//#pragma mark - +// +//@implementation DataSourceURL +// +//+ (id)dataSourceWithURL:(NSURL *)fileUrl; +//{ +// DataSourceValue *instance = [DataSourceValue new]; +// instance.fileUrl = fileUrl; +// return instance; +//} +// +//- (NSData *)data +//{ +// OWSAssert(self.filePath); +// +// @synchronized (self) { +// if (!self.cachedData) { +// self.cachedData = [NSData dataWithContentsOfFile:self.filePath]; +// } +// if (!self.cachedData) { +// OWSFail(@"%@ Could not read data from disk: %@", self.tag, self.filePath); +// self.cachedData = [NSData new]; +// } +// return self.cachedData; +// } +//} +// +//- (nullable NSURL *)dataUrl:(NSString *)fileExtension +//{ +// OWSAssert(self.filePath); +// +// return [NSURL fileURLWithPath:self.filePath]; +//} +// +//- (nullable NSString *)dataPath:(NSString *)fileExtension +//{ +// OWSAssert(self.filePath); +// +// return self.filePath; +//} +// +//- (nullable NSString *)dataPathIfOnDisk +//{ +// OWSAssert(self.filePath); +// +// return self.filePath; +//} +// +//- (NSUInteger)dataLength +//{ +// OWSAssert(self.filePath); +// +// @synchronized (self) { +// if (!self.cachedDataLength) { +// NSError *error; +// NSDictionary *_Nullable attributes = +// [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error]; +// if (!attributes || error) { +// OWSFail(@"%@ Could not read data length from disk: %@", self.tag, self.filePath); +// self.cachedDataLength = @(0); +// } else { +// uint64_t fileSize = [attributes fileSize]; +// self.cachedDataLength = @(fileSize); +// } +// } +// return [self.cachedDataLength unsignedIntegerValue]; +// } +//} +// +//#pragma mark - Logging +// +//+ (NSString *)tag +//{ +// return [NSString stringWithFormat:@"[%@]", self.class]; +//} +// +//- (NSString *)tag +//{ +// return self.class.tag; +//} +// +//@end +// +//#pragma mark - +// +// +//@objc class DataSourcePath : NSObject, DataSource { +// static let TAG = "[DataSourcePath]" +// +// private let path : String +// +// private var cachedData : Data +// ? +// +// private var cachedLength +// : Int +// ? +// +// // MARK: Constructor +// +// internal required init(_ path +// : String){ self.path = path super.init() } +// +// func +// data() +// ->Data +// { +// if +// let cachedData +// = cachedData{ return cachedData } Logger.error("\(DataSourcePath.TAG) reading data: \(path)") do +// { +// try +// cachedData = NSData(contentsOfFile : path) as Data +// } +// catch +// { +// owsFail("\(DataSourcePath.TAG) Could not read data from disk: \(path)") cachedData = Data() +// } +// return cachedData ! +// } +// +// return cachedLength ! +// } +//} +// +//@objc class DataSourceUrl : NSObject, +// DataSource { +// static let TAG = "[DataSourceUrl]" +// +// private let url : URL +// +// private var cachedData : Data +// ? +// +// private var cachedLength +// : Int +// ? +// +// // MARK: Constructor +// +// internal required +// init(_ url +// : URL) +// { +// if +// !url.isFileURL{ owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") } self.url = url +// super.init() +// } +// +// func data()->Data +// { +// if +// let cachedData +// = cachedData{ return cachedData } guard url +// .isFileURL else { +// owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") return Data() +// } Logger.error("\(DataSourceUrl.TAG) reading data: \(url)") do +// { +// try +// cachedData = Data(contentsOf : url) +// } +// catch +// { +// owsFail("\(DataSourceUrl.TAG) Could not read data from disk: \(url)") cachedData = Data() +// } +// return cachedData ! +// } +// +// func dataUrl(fileExtension +// : String) +// ->URL +// ? { return url } +// +// func dataPath(fileExtension +// : String) +// ->String +// ? { guard url +// .isFileURL else { +// owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") return nil } return url.path } +// +// func dataPathIfOnDisk() +// ->String +// ? { guard url +// .isFileURL else { +// owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") return nil } return url.path } +// +// func dataLength() +// ->Int +// { +// if +// let cachedLength = cachedLength{ return cachedLength } guard url.isFileURL else +// { +// owsFail("\(DataSourceUrl.TAG) URL is not a file URL: \(url)") return 0 +// } +// +// do { +// let fileAttributes = try +// FileManager.default.attributesOfItem(atPath +// : url.path) let fileSize +// = fileAttributes[FileAttributeKey.size] as !UInt64 cachedLength = Int(fileSize) +// } +// catch +// { +// owsFail("\(DataSourceUrl.TAG) Could not read data length from disk: \(url)") cachedLength = 0 +// } +// +// return cachedLength ! +// } +//} + +NS_ASSUME_NONNULL_END