Add data source class.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent 94f02c0d1d
commit 2282733fa9

@ -68,7 +68,6 @@
34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */; };
34B3F8901E8DF1710035BE1A /* AppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */; };
34B3F8911E8DF1710035BE1A /* ShowGroupMembersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */; };
34B3F8921E8DF1710035BE1A /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */; };
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
34B3F8941E8DF1710035BE1A /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8701E8DF1700035BE1A /* HomeViewController.m */; };
34B3F8991E8DF1B90035BE1A /* TSMessageAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8981E8DF1B90035BE1A /* TSMessageAdapterTest.m */; };
@ -90,6 +89,8 @@
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 */; };
34D99C941F2937CC00D284D6 /* OWSSwiftUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C921F2937CC00D284D6 /* OWSSwiftUtils.swift */; };
@ -507,7 +508,6 @@
34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSettingsViewController.m; sourceTree = "<group>"; };
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowGroupMembersViewController.h; sourceTree = "<group>"; };
34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowGroupMembersViewController.m; sourceTree = "<group>"; };
34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
34B3F86D1E8DF1700035BE1A /* SignalsNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalsNavigationController.h; sourceTree = "<group>"; };
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsNavigationController.m; sourceTree = "<group>"; };
34B3F86F1E8DF1700035BE1A /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = "<group>"; };
@ -551,6 +551,8 @@
34D8C0261ED3673300188D7C /* DebugUITableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DebugUITableViewController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
34D8C0291ED3685800188D7C /* DebugUIContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIContacts.h; sourceTree = "<group>"; };
34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = "<group>"; };
34D913481F62D4A500722898 /* DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = "<group>"; };
34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = "<group>"; };
34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = "<group>"; };
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
@ -1077,7 +1079,6 @@
3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */,
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */,
34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */,
34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */,
34B3F86D1E8DF1700035BE1A /* SignalsNavigationController.h */,
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
3400C7971EAFB772008A8584 /* ThreadViewHelper.h */,
@ -1230,6 +1231,7 @@
children = (
45CD81EE1DC030E7004C9430 /* AccountManager.swift */,
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */,
34D913481F62D4A500722898 /* DataSource.swift */,
45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */,
45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */,
45C681B51D305A580050903A /* OWSCall.h */,
@ -1244,6 +1246,7 @@
45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */,
453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */,
453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */,
34D913491F62D4A500722898 /* SignalAttachment.swift */,
B62D53F41A23CC8B009AAF82 /* TSMessageAdapters */,
34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */,
34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */,
@ -2210,6 +2213,7 @@
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */,
45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */,
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */,
34D9134B1F62D4A500722898 /* SignalAttachment.swift in Sources */,
34B3F88E1E8DF1700035BE1A /* PrivacySettingsTableViewController.m in Sources */,
4505C2C21E648F7A00CEBF41 /* ExperienceUpgradeFinder.swift in Sources */,
341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */,
@ -2285,7 +2289,6 @@
34B3F87C1E8DF1700035BE1A /* FingerprintViewController.m in Sources */,
45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */,
76EB058218170B33006006FC /* Environment.m in Sources */,
34B3F8921E8DF1710035BE1A /* SignalAttachment.swift in Sources */,
450449391F45EE7D002D1ADA /* NSString+OWS.m in Sources */,
45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */,
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
@ -2322,6 +2325,7 @@
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 */,

@ -364,19 +364,10 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
@"Alert body when picking a document fails because user picked a directory/bundle")];
return NO;
}
NSData *data = [NSData dataWithContentsOfURL:url];
if (!data) {
DDLogError(@"Application opened with URL with unloadable content: %@", url);
[OWSAlerts showAlertWithTitle:
NSLocalizedString(@"EXPORT_WITH_SIGNAL_ERROR_TITLE",
@"Title for the alert indicating the 'export with signal' attachment had an error.")
message:NSLocalizedString(@"EXPORT_WITH_SIGNAL_ERROR_MESSAGE_MISSING_DATA",
@"Message for the alert indicating the 'export with signal' data "
@"couldn't be loaded.")];
return NO;
}
SignalAttachment *attachment = [SignalAttachment attachmentWithData:data dataUTI:utiType filename:filename];
DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:url];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename];
if (!attachment) {
DDLogError(@"Application opened with URL with invalid content: %@", url);
[OWSAlerts showAlertWithTitle:

@ -0,0 +1,217 @@
//
// 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!
}
}

@ -60,9 +60,18 @@ class SignalAttachment: NSObject {
// MARK: Properties
let data: Data
let dataSource: DataSource
internal var temporaryDataUrl: URL?
// TODO: Rename
public var data: Data {
return dataSource.data()
}
public var dataLength: Int {
return dataSource.dataLength()
}
public var dataUrl: URL? {
return dataSource.dataUrl(fileExtension:fileExtensionNonNil)
}
// Attachment types are identified using UTIs.
//
@ -108,8 +117,8 @@ class SignalAttachment: NSObject {
// This method should not be called directly; use the factory
// methods instead.
internal required init(data: Data, dataUTI: String, filename: String?) {
self.data = data
internal required init(dataSource: DataSource, dataUTI: String, filename: String?) {
self.dataSource = dataSource
self.dataUTI = dataUTI
self.filename = filename
super.init()
@ -117,27 +126,6 @@ class SignalAttachment: NSObject {
// MARK: Methods
public func getTemporaryDataUrl() -> URL? {
if temporaryDataUrl == nil {
let directory = NSTemporaryDirectory()
guard let fileExtension = self.fileExtension else {
return nil
}
let fileName = NSUUID().uuidString + "." + fileExtension
guard let fileUrl = NSURL.fileURL(withPathComponents: [directory, fileName]) else {
return nil
}
do {
try data.write(to: fileUrl)
} catch {
owsFail("\(SignalAttachment.TAG) Could not write data to disk: \(dataUTI)")
return nil
}
temporaryDataUrl = fileUrl
}
return temporaryDataUrl
}
var hasError: Bool {
return error != nil
}
@ -245,6 +233,15 @@ class SignalAttachment: NSObject {
return fileExtension
}
// Returns the file extension for this attachment or nil if no file extension
// can be identified.
var fileExtensionNonNil: String {
if let fileExtension = fileExtension {
return fileExtension
}
return ""
}
// Returns the set of UTIs that correspond to valid _input_ image formats
// for Signal attachments.
//
@ -305,6 +302,15 @@ class SignalAttachment: NSObject {
return SignalAttachment.audioUTISet.contains(dataUTI)
}
public var isValidImage: Bool {
if let dataPath = dataSource.dataPathIfOnDisk() {
return NSData.ows_isValidImage(atPath:dataPath)
}
let data = dataSource.data()
return (data as NSData).ows_isValidImage()
}
public class func pasteboardHasPossibleAttachment() -> Bool {
return UIPasteboard.general.numberOfItems > 0
}
@ -373,7 +379,8 @@ class SignalAttachment: NSObject {
owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)")
return nil
}
return imageAttachment(data : data, dataUTI : dataUTI, filename: nil)
let dataSource = DataSourceValue(data)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
}
}
for dataUTI in videoUTISet {
@ -382,7 +389,8 @@ class SignalAttachment: NSObject {
owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)")
return nil
}
return videoAttachment(data : data, dataUTI : dataUTI, filename: nil)
let dataSource = DataSourceValue(data)
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
}
}
for dataUTI in audioUTISet {
@ -391,7 +399,8 @@ class SignalAttachment: NSObject {
owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)")
return nil
}
return audioAttachment(data : data, dataUTI : dataUTI, filename: nil)
let dataSource = DataSourceValue(data)
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
}
}
@ -400,7 +409,8 @@ class SignalAttachment: NSObject {
owsFail("\(TAG) Missing expected pasteboard data for UTI: \(dataUTI)")
return nil
}
return genericAttachment(data : data, dataUTI : dataUTI, filename: nil)
let dataSource = DataSourceValue(data)
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: nil)
}
// This method should only be called for dataUTIs that
@ -428,31 +438,31 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func imageAttachment(data imageData: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
private class func imageAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(imageData != nil)
guard let imageData = imageData else {
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI, filename: filename)
assert(dataSource != nil)
guard let dataSource = dataSource else {
let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(data : imageData, dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI, filename: filename)
guard inputImageUTISet.contains(dataUTI) else {
attachment.error = .invalidFileFormat
return attachment
}
guard imageData.count > 0 else {
guard dataSource.dataLength() > 0 else {
owsFail("\(self.TAG) in \(#function) imageData was empty")
attachment.error = .invalidData
return attachment
}
if animatedImageUTISet.contains(dataUTI) {
guard imageData.count <= kMaxFileSizeAnimatedImage else {
guard dataSource.dataLength() <= kMaxFileSizeAnimatedImage else {
attachment.error = .fileSizeTooLarge
return attachment
}
@ -460,13 +470,13 @@ class SignalAttachment: NSObject {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType) to retain any animation")
return attachment
} else {
guard let image = UIImage(data:imageData) else {
guard let image = UIImage(data:dataSource.data()) else {
attachment.error = .couldNotParseImage
return attachment
}
attachment.image = image
if isInputImageValidOutputImage(image: image, imageData: imageData, dataUTI: dataUTI) {
if isInputImageValidOutputImage(image: image, dataSource: dataSource, dataUTI: dataUTI) {
Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)")
return attachment
}
@ -483,11 +493,11 @@ class SignalAttachment: NSObject {
// If the proposed attachment already conforms to the
// file size and content size limits, don't recompress it.
private class func isInputImageValidOutputImage(image: UIImage?, imageData: Data?, dataUTI: String) -> Bool {
private class func isInputImageValidOutputImage(image: UIImage?, dataSource: DataSource?, dataUTI: String) -> Bool {
guard let image = image else {
return false
}
guard let imageData = imageData else {
guard let dataSource = dataSource else {
return false
}
guard SignalAttachment.outputImageUTISet.contains(dataUTI) else {
@ -498,7 +508,7 @@ class SignalAttachment: NSObject {
imageUploadQuality:defaultImageUploadQuality())
if image.size.width <= maxSize &&
image.size.height <= maxSize &&
imageData.count <= kMaxFileSizeImage {
dataSource.dataLength() <= kMaxFileSizeImage {
return true
}
return false
@ -512,13 +522,13 @@ class SignalAttachment: NSObject {
assert(dataUTI.characters.count > 0)
guard let image = image else {
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename)
attachment.error = .missingData
return attachment
}
// Make a placeholder attachment on which to hang errors if necessary.
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename)
attachment.image = image
Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg")
@ -544,7 +554,7 @@ class SignalAttachment: NSObject {
}
if jpgImageData.count <= kMaxFileSizeImage {
let recompressedAttachment = SignalAttachment(data : jpgImageData, dataUTI: kUTTypeJPEG as String, filename: filename)
let recompressedAttachment = SignalAttachment(dataSource : DataSourceValue(jpgImageData), dataUTI: kUTTypeJPEG as String, filename: filename)
recompressedAttachment.image = dstImage
return recompressedAttachment
}
@ -614,8 +624,8 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func videoAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(data : data,
private class func videoAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : videoUTISet,
maxFileSize : kMaxFileSizeVideo,
@ -628,8 +638,8 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func audioAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(data : data,
private class func audioAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : audioUTISet,
maxFileSize : kMaxFileSizeAudio,
@ -643,7 +653,11 @@ 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 {
return newAttachment(data : text?.data(using: .utf8),
var dataSource = DataSourceValue.empty()
if let data = text?.data(using: .utf8) {
dataSource = DataSourceValue(data)
}
return newAttachment(dataSource : dataSource,
dataUTI : kOversizeTextAttachmentUTI,
validUTISet : nil,
maxFileSize : kMaxFileSizeGeneric,
@ -656,8 +670,8 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
private class func genericAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(data : data,
private class func genericAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
return newAttachment(dataSource : dataSource,
dataUTI : dataUTI,
validUTISet : nil,
maxFileSize : kMaxFileSizeGeneric,
@ -666,8 +680,8 @@ class SignalAttachment: NSObject {
// MARK: Voice Messages
public class func voiceMessageAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
let attachment = audioAttachment(data : data, dataUTI : dataUTI, filename: filename)
public class func voiceMessageAttachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
let attachment = audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
attachment.isVoiceMessage = true
return attachment
}
@ -678,35 +692,41 @@ class SignalAttachment: NSObject {
//
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
public class func attachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment {
public class func attachment(dataSource: DataSource?, dataUTI: String, filename: String?) -> SignalAttachment {
if inputImageUTISet.contains(dataUTI) {
return imageAttachment(data : data, dataUTI : dataUTI, filename: filename)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
} else if videoUTISet.contains(dataUTI) {
return videoAttachment(data : data, dataUTI : dataUTI, filename: filename)
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
} else if audioUTISet.contains(dataUTI) {
return audioAttachment(data : data, dataUTI : dataUTI, filename: filename)
return audioAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
} else {
return genericAttachment(data : data, dataUTI : dataUTI, filename: filename)
return genericAttachment(dataSource : dataSource, dataUTI : dataUTI, filename: filename)
}
}
public class func empty() -> SignalAttachment {
return SignalAttachment.attachment(dataSource : DataSourceValue.empty(),
dataUTI: kUTTypeContent as String,
filename:nil)
}
// MARK: Helper Methods
private class func newAttachment(data: Data?,
private class func newAttachment(dataSource: DataSource?,
dataUTI: String,
validUTISet: Set<String>?,
maxFileSize: Int,
filename: String?) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(data != nil)
guard let data = data else {
let attachment = SignalAttachment(data : Data(), dataUTI: dataUTI, filename: filename)
assert(dataSource != nil)
guard let dataSource = dataSource else {
let attachment = SignalAttachment(dataSource : DataSourceValue.empty(), dataUTI: dataUTI, filename: filename)
attachment.error = .missingData
return attachment
}
let attachment = SignalAttachment(data : data, dataUTI: dataUTI, filename: filename)
let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI, filename: filename)
if let validUTISet = validUTISet {
guard validUTISet.contains(dataUTI) else {
@ -715,13 +735,14 @@ class SignalAttachment: NSObject {
}
}
guard data.count > 0 else {
assert(data.count > 0)
guard dataSource.dataLength() > 0 else {
owsFail("Empty attachment")
assert(dataSource.dataLength() > 0)
attachment.error = .invalidData
return attachment
}
guard data.count <= maxFileSize else {
guard dataSource.dataLength() <= maxFileSize else {
attachment.error = .fileSizeTooLarge
return attachment
}

@ -29,9 +29,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
@available(*, unavailable, message:"use attachment: constructor instead.")
required init?(coder aDecoder: NSCoder) {
self.attachment = SignalAttachment.attachment(data: nil,
dataUTI: kUTTypeContent as String,
filename:nil)
self.attachment = SignalAttachment.empty()
super.init(coder: aDecoder)
owsFail("\(self.TAG) invalid constructor")
}
@ -130,7 +128,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
}
private func createAudioPreview(attachmentPreviewView: UIView) {
guard let dataUrl = attachment.getTemporaryDataUrl() else {
guard let dataUrl = attachment.dataUrl else {
createGenericPreview(attachmentPreviewView:attachmentPreviewView)
return
}
@ -171,11 +169,10 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
}
private func createAnimatedPreview(attachmentPreviewView: UIView) {
let data = attachment.data
guard (data as NSData).ows_isValidImage() else {
guard attachment.isValidImage else {
return
}
let data = attachment.data
// Use Flipboard FLAnimatedImage library to display gifs
guard let animatedImage = FLAnimatedImage(gifData:data) else {
createGenericPreview(attachmentPreviewView:attachmentPreviewView)
@ -209,7 +206,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
}
private func createVideoPreview(attachmentPreviewView: UIView) {
guard let dataUrl = attachment.getTemporaryDataUrl() else {
guard let dataUrl = attachment.dataUrl else {
createGenericPreview(attachmentPreviewView:attachmentPreviewView)
return
}
@ -314,7 +311,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla
private func createFileSizeLabel() -> UIView {
let label = UILabel()
let fileSize = attachment.data.count
let fileSize = attachment.dataLength
label.text = String(format:NSLocalizedString("ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT",
comment: "Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}."),
ViewControllerUtils.formatFileSize(UInt(fileSize)))

@ -1673,10 +1673,11 @@ 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]];
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:[text dataUsingEncoding:NSUTF8StringEncoding]
dataUTI:SignalAttachment.kOversizeTextAttachmentUTI
filename:nil];
[SignalAttachment attachmentWithDataSource:dataSource
dataUTI:SignalAttachment.kOversizeTextAttachmentUTI
filename:nil];
message =
[ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread messageSender:self.messageSender];
} else {
@ -3157,7 +3158,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{
DDLogDebug(@"%@ Picked document at url: %@", self.tag, url);
NSData *attachmentData = [NSData dataWithContentsOfURL:url];
NSString *type;
NSError *typeError;
@ -3206,28 +3206,11 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
@"ATTACHMENT_DEFAULT_FILENAME", @"Generic filename for an attachment with no known name");
}
if (!attachmentData || attachmentData.length == 0) {
OWSFail(@"%@ attachment data was unexpectedly empty for picked document url: %@", self.tag, url);
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE",
@"Alert title when picking a document fails for an unknown reason")
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *dismissAction =
[UIAlertAction actionWithTitle:CommonStrings.dismissButton style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:dismissAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alertController animated:YES completion:nil];
});
return;
}
OWSAssert(attachmentData);
OWSAssert(type);
OWSAssert(filename);
SignalAttachment *attachment = [SignalAttachment attachmentWithData:attachmentData dataUTI:type filename:filename];
DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:url];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:type filename:filename];
[self tryToSendAttachmentIfApproved:attachment];
}
@ -3393,8 +3376,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
}
OWSAssert([NSThread isMainThread]);
DataSourceValue *dataSource = [[DataSourceValue alloc] init:imageData];
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:imageData dataUTI:dataUTI filename:filename];
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:dataUTI filename:filename];
[self dismissViewControllerAnimated:YES
completion:^{
OWSAssert([NSThread isMainThread]);
@ -3422,7 +3406,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
OWSAssert([attachment mimeType].length > 0);
DDLogVerbose(@"Sending attachment. Size in bytes: %lu, contentType: %@",
(unsigned long)attachment.data.length,
(unsigned long)[attachment dataLength],
[attachment mimeType]);
BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
TSOutgoingMessage *message =
@ -3440,16 +3424,17 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
- (NSURL *)videoTempFolder
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
basePath = [basePath stringByAppendingPathComponent:@"videos"];
if (![[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:basePath
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
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
return [NSURL fileURLWithPath:basePath];
return [NSURL fileURLWithPath:videoDirPath];
}
- (void)sendQualityAdjustedAttachmentForVideo:(NSURL *)movieURL
@ -3461,20 +3446,17 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeMPEG4;
double currentTime = [[NSDate date] timeIntervalSince1970];
NSString *strImageName = [NSString stringWithFormat:@"%f", currentTime];
NSURL *compressedVideoUrl =
[[self videoTempFolder] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4", strImageName]];
NSURL *compressedVideoUrl = [[self videoTempFolder]
URLByAppendingPathComponent:[[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:@"mp4"]];
exportSession.outputURL = compressedVideoUrl;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSData *videoData = [NSData dataWithContentsOfURL:compressedVideoUrl];
dispatch_async(dispatch_get_main_queue(), ^{
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:videoData dataUTI:(NSString *)kUTTypeMPEG4 filename:filename];
DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:compressedVideoUrl];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypeMPEG4
filename:filename];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
DDLogError(@"%@ %s Invalid attachment: %@.",
self.tag,
__PRETTY_FUNCTION__,
attachment ? [attachment errorName] : @"Missing data");
@ -3482,12 +3464,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
} else {
[self tryToSendAttachmentIfApproved:attachment skipApprovalDialog:skipApprovalDialog];
}
NSError *error;
[[NSFileManager defaultManager] removeItemAtURL:compressedVideoUrl error:&error];
if (error) {
DDLogWarn(@"Failed to remove cached video file: %@", error.debugDescription);
}
});
}];
}
@ -3852,23 +3828,15 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
return;
}
NSData *audioData = [NSData dataWithContentsOfURL:self.audioRecorder.url];
if (!audioData) {
DDLogError(@"%@ Couldn't load audioRecorder data", self.tag);
OWSAssert(0);
self.audioRecorder = nil;
return;
}
DataSourceUrl *dataSource = [[DataSourceUrl alloc] init:self.audioRecorder.url];
self.audioRecorder = nil;
NSString *filename = [NSLocalizedString(@"VOICE_MESSAGE_FILE_NAME", @"Filename for voice messages.")
stringByAppendingPathExtension:@"m4a"];
SignalAttachment *attachment = [SignalAttachment voiceMessageAttachmentWithData:audioData
dataUTI:(NSString *)kUTTypeMPEG4Audio
filename:filename];
SignalAttachment *attachment = [SignalAttachment voiceMessageAttachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypeMPEG4Audio
filename:filename];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.tag,
@ -4292,15 +4260,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
DDLogError(@"%@ %s: %@", self.tag, __PRETTY_FUNCTION__, errorMessage);
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"ATTACHMENT_ERROR_ALERT_TITLE",
@"The title of the 'attachment error' alert.")
message:errorMessage
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
[OWSAlerts showAlertWithTitle:NSLocalizedString(
@"ATTACHMENT_ERROR_ALERT_TITLE", @"The title of the 'attachment error' alert.")
message:errorMessage];
}
- (void)textViewDidChangeLayout

@ -328,14 +328,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSMessageSender *messageSender = [Environment getCurrent].messageSender;
NSString *filename = [filePath lastPathComponent];
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
NSData *data = [NSData dataWithContentsOfFile:filePath];
OWSAssert(data);
if (!data) {
DDLogError(@"Couldn't read attachment: %@", filePath);
failure();
return;
}
SignalAttachment *attachment = [SignalAttachment attachmentWithData:data dataUTI:utiType filename:filename];
DataSourcePath *dataSource = [[DataSourcePath alloc] init:filePath];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType filename:filename];
OWSAssert(attachment);
if ([attachment hasError]) {
DDLogError(@"attachment[%@]: %@", [attachment filename], [attachment errorName]);
@ -593,9 +588,11 @@ NS_ASSUME_NONNULL_BEGIN
@"lorem, in rhoncus nisi."];
}
SignalAttachment *attachment = [SignalAttachment attachmentWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
dataUTI:SignalAttachment.kOversizeTextAttachmentUTI
filename:nil];
DataSourceValue *dataSource = [[DataSourceValue alloc] init:[message dataUsingEncoding:NSUTF8StringEncoding]];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource
dataUTI:SignalAttachment.kOversizeTextAttachmentUTI
filename:nil];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender];
}
@ -619,8 +616,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti length:(NSUInteger)length
{
OWSMessageSender *messageSender = [Environment getCurrent].messageSender;
SignalAttachment *attachment =
[SignalAttachment attachmentWithData:[self createRandomNSDataOfSize:length] dataUTI:uti filename:nil];
DataSourceValue *dataSource = [[DataSourceValue alloc] init:[self createRandomNSDataOfSize:length]];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti filename:nil];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender ignoreErrors:YES];
}
+ (OWSSignalServiceProtosEnvelope *)createEnvelopeForThread:(TSThread *)thread

@ -250,6 +250,7 @@ const NSUInteger kAvatarViewDiameter = 52;
self.avatarView.image =
[OWSAvatarBuilder buildImageForThread:thread diameter:kAvatarViewDiameter contactsManager:contactsManager];
}
#pragma mark - Date formatting
- (NSAttributedString *)dateAttributedString:(NSDate *)date {

Loading…
Cancel
Save