Allow customizable attachments to feedback

- New `BITHockeyAttachment` class, which `BITCrashAttachment` is now a subclass of
- Can also be used in `prepareWithItems:` for BITFeedbackComposeViewController
- Allows to set a custom filename and content-type for attachments that can be used with crash reports and with feedback
This commit is contained in:
Andreas Linde
2014-06-21 16:23:55 +02:00
parent 5fc0014385
commit cae1226b38
14 changed files with 228 additions and 74 deletions

View File

@@ -26,38 +26,31 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
#import "BITHockeyAttachment.h"
/**
* Provides support to add binary attachments to crash reports
*
* This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]`
Deprecated: Provides support to add binary attachments to crash reports
This class is not needed any longer and exists for compatiblity purposes with
HockeySDK-iOS 3.5.5.
It is a subclass of `BITHockeyAttachment` which only provides an initializer
that is compatible with the one of HockeySDK-iOS 3.5.5.
This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]`
@see BITHockeyAttachment
*/
@interface BITCrashAttachment : NSObject<NSCoding>
@interface BITCrashAttachment : BITHockeyAttachment
/**
* The filename the attachment should get
*/
@property (nonatomic, readonly, strong) NSString *filename;
/**
* The attachment data as NSData object
*/
@property (nonatomic, readonly, strong) NSData *crashAttachmentData;
/**
* The content type of your data as MIME type
*/
@property (nonatomic, readonly, strong) NSString *contentType;
/**
* Create an BITCrashAttachment instance with a given filename and NSData object
*
* @param filename The filename the attachment should get
* @param crashAttachmentData The attachment data as NSData
* @param contentType The content type of your data as MIME type
*
* @return An instsance of BITCrashAttachment
Create an BITCrashAttachment instance with a given filename and NSData object
@param filename The filename the attachment should get
@param crashAttachmentData The attachment data as NSData
@param contentType The content type of your data as MIME type
@return An instsance of BITCrashAttachment
*/
- (instancetype)initWithFilename:(NSString *)filename
crashAttachmentData:(NSData *)crashAttachmentData

View File

@@ -34,31 +34,9 @@
crashAttachmentData:(NSData *)crashAttachmentData
contentType:(NSString *)contentType
{
if (self = [super init]) {
_filename = filename;
_crashAttachmentData = crashAttachmentData;
_contentType = contentType;
}
self = [super initWithFilename:filename hockeyAttachmentData:crashAttachmentData contentType:contentType];
return self;
}
#pragma mark - NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.filename forKey:@"filename"];
[encoder encodeObject:self.crashAttachmentData forKey:@"data"];
[encoder encodeObject:self.contentType forKey:@"contentType"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
_filename = [decoder decodeObjectForKey:@"filename"];
_crashAttachmentData = [decoder decodeObjectForKey:@"data"];
_contentType = [decoder decodeObjectForKey:@"contentType"];
}
return self;
}
@end

View File

@@ -278,7 +278,7 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
}
}
- (void)persistAttachment:(BITCrashAttachment *)attachment withFilename:(NSString *)filename {
- (void)persistAttachment:(BITHockeyAttachment *)attachment withFilename:(NSString *)filename {
NSString *attachmentFilename = [filename stringByAppendingString:@".data"];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
@@ -318,9 +318,9 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
*
* @param filename The crash report file path
*
* @return an BITCrashAttachment instance or nil
* @return an BITHockeyAttachment instance or nil
*/
- (BITCrashAttachment *)attachmentForCrashReport:(NSString *)filename {
- (BITHockeyAttachment *)attachmentForCrashReport:(NSString *)filename {
NSString *attachmentFilename = [filename stringByAppendingString:@".data"];
if (![_fileManager fileExistsAtPath:attachmentFilename])
@@ -341,7 +341,7 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
}
if ([unarchiver containsValueForKey:kBITCrashMetaAttachment]) {
BITCrashAttachment *attachment = [unarchiver decodeObjectForKey:kBITCrashMetaAttachment];
BITHockeyAttachment *attachment = [unarchiver decodeObjectForKey:kBITCrashMetaAttachment];
return attachment;
}
@@ -701,9 +701,9 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
[metaDict setObject:applicationLog forKey:kBITCrashMetaApplicationLog];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(attachmentForCrashManager:)]) {
BITCrashAttachment *attachment = [self.delegate attachmentForCrashManager:self];
BITHockeyAttachment *attachment = [self.delegate attachmentForCrashManager:self];
if (attachment) {
if (attachment && attachment.hockeyAttachmentData) {
[self persistAttachment:attachment withFilename:[_crashesDir stringByAppendingPathComponent: filename]];
}
}
@@ -1219,7 +1219,7 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
return;
NSString *crashXML = nil;
BITCrashAttachment *attachment = nil;
BITHockeyAttachment *attachment = nil;
NSString *filename = [_crashFiles objectAtIndex:0];
NSString *cacheFilename = [filename lastPathComponent];
@@ -1378,7 +1378,7 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
#pragma mark - Networking
- (NSURLRequest *)requestWithXML:(NSString*)xml attachment:(BITCrashAttachment *)attachment {
- (NSURLRequest *)requestWithXML:(NSString*)xml attachment:(BITHockeyAttachment *)attachment {
NSString *postCrashPath = [NSString stringWithFormat:@"api/2/apps/%@/crashes", self.encodedAppIdentifier];
NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST"
@@ -1413,12 +1413,16 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
boundary:boundary
filename:@"crash.xml"]];
if (attachment) {
[postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.crashAttachmentData
if (attachment && attachment.hockeyAttachmentData) {
NSString *attachmentFilename = attachment.filename;
if (!attachmentFilename) {
attachmentFilename = @"Attachment_0";
}
[postBody appendData:[BITHockeyAppClient dataWithPostValue:attachment.hockeyAttachmentData
forKey:@"attachment0"
contentType:attachment.contentType
boundary:boundary
filename:attachment.filename]];
filename:attachmentFilename]];
}
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
@@ -1435,7 +1439,7 @@ static PLCrashReporterCallbacks plCrashCallbacks = {
*
* @param xml The XML data that needs to be send to the server
*/
- (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITCrashAttachment *)attachment {
- (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITHockeyAttachment *)attachment {
NSURLRequest* request = [self requestWithXML:xml attachment:attachment];

View File

@@ -29,7 +29,7 @@
#import <Foundation/Foundation.h>
@class BITCrashManager;
@class BITCrashAttachment;
@class BITHockeyAttachment;
/**
The `BITCrashManagerDelegate` formal protocol defines methods further configuring
@@ -55,28 +55,29 @@
-(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager;
/** Return a BITCrashAttachment object providing an NSData object the crash report
/** Return a BITHockeyAttachment object providing an NSData object the crash report
being processed should contain
Please limit your attachments to reasonable files to avoid high traffic costs for your users.
Example implementation:
- (BITCrashAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager {
- (BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager {
NSData *data = [NSData dataWithContentsOfURL:@"mydatafile"];
BITCrashAttachment *attachment = [[BITCrashAttachment alloc] initWithFilename:@"myfile.data"
crashAttachmentData:data
contentType:@"'application/octet-stream"];
BITHockeyAttachment *attachment = [[BITHockeyAttachment alloc] initWithFilename:@"myfile.data"
hockeyAttachmentData:data
contentType:@"'application/octet-stream"];
return attachment;
}
@param crashManager The `BITCrashManager` instance invoking this delegate
@see BITHockeyAttachment
@see applicationLogForCrashManager:
@see userNameForCrashManager:
@see userEmailForCrashManager:
*/
-(BITCrashAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager;
-(BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager;

View File

@@ -94,9 +94,9 @@
- (NSString *)firstNotApprovedCrashReport;
- (void)persistUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData;
- (void)persistAttachment:(BITCrashAttachment *)attachment withFilename:(NSString *)filename;
- (void)persistAttachment:(BITHockeyAttachment *)attachment withFilename:(NSString *)filename;
- (BITCrashAttachment *)attachmentForCrashReport:(NSString *)filename;
- (BITHockeyAttachment *)attachmentForCrashReport:(NSString *)filename;
- (void)invokeDelayedProcessing;
- (void)sendNextCrashReport;

View File

@@ -38,6 +38,7 @@
#import "BITFeedbackManagerPrivate.h"
#import "BITHockeyBaseManagerPrivate.h"
#import "BITHockeyAttachment.h"
@interface BITFeedbackActivity()
@@ -100,6 +101,8 @@
return YES;
} else if ([item isKindOfClass:[NSData class]]) {
return YES;
} else if ([item isKindOfClass:[BITHockeyAttachment class]]) {
return YES;
} else if ([item isKindOfClass:[NSURL class]]) {
return YES;
}
@@ -112,6 +115,7 @@
if ([item isKindOfClass:[NSString class]] ||
[item isKindOfClass:[UIImage class]] ||
[item isKindOfClass:[NSData class]] ||
[item isKindOfClass:[BITHockeyAttachment class]] ||
[item isKindOfClass:[NSURL class]]) {
[_items addObject:item];
} else {

View File

@@ -77,6 +77,7 @@
- NSURL
- UIImage
- NSData
- `BITHockeyAttachment`
These are automatically concatenated to one text string, while any images and NSData
objects are added as attachments to the feedback.

View File

@@ -43,6 +43,7 @@
#import "BITHockeyHelper.h"
#import "BITImageAnnotationViewController.h"
#import "BITHockeyAttachment.h"
@interface BITFeedbackComposeViewController () <BITFeedbackUserDataDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate, BITImageAnnotationDelegate> {
@@ -114,6 +115,22 @@
BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:item contentType:@"application/octet-stream"];
attachment.originalFilename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]];
[self.attachments addObject:attachment];
} else if ([item isKindOfClass:[BITHockeyAttachment class]]) {
BITHockeyAttachment *sourceAttachment = (BITHockeyAttachment *)item;
if (!sourceAttachment.hockeyAttachmentData) {
BITHockeyLog(@"BITHockeyAttachment instance doesn't contain any data.");
continue;
}
NSString *filename = [NSString stringWithFormat:@"Attachment_%li.data", (unsigned long)[self.attachments count]];
if (sourceAttachment.filename) {
filename = sourceAttachment.filename;
}
BITFeedbackMessageAttachment *attachment = [BITFeedbackMessageAttachment attachmentWithData:sourceAttachment.hockeyAttachmentData contentType:sourceAttachment.contentType];
attachment.originalFilename = filename;
[self.attachments addObject:attachment];
} else {
BITHockeyLog(@"Unknown item type %@", item);
}

View File

@@ -289,6 +289,7 @@ typedef NS_ENUM(NSInteger, BITFeedbackObservationMode) {
while all UIImage and NSData-instances will be turned into attachments.
@param items an NSArray with objects that should be attached
@see `[BITFeedbackComposeViewController prepareWithItems:]`
*/
- (void)showFeedbackComposeViewWithPreparedItems:(NSArray *)items;

View File

@@ -0,0 +1,68 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2014 HockeyApp, Bit Stadium GmbH.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
/**
Provides support to add binary attachments to crash reports and feedback messages
This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]`,
`[BITFeedbackComposeViewController prepareWithItems:]` and
`[BITFeedbackManager showFeedbackComposeViewWithPreparedItems:]`
*/
@interface BITHockeyAttachment : NSObject<NSCoding>
/**
The filename the attachment should get
*/
@property (nonatomic, readonly, strong) NSString *filename;
/**
The attachment data as NSData object
*/
@property (nonatomic, readonly, strong) NSData *hockeyAttachmentData;
/**
The content type of your data as MIME type
*/
@property (nonatomic, readonly, strong) NSString *contentType;
/**
Create an BITHockeyAttachment instance with a given filename and NSData object
@param filename The filename the attachment should get. If nil will get a automatically generated filename
@param hockeyAttachmentData The attachment data as NSData. The instance will be ignore if this is set to nil!
@param contentType The content type of your data as MIME type. If nil will be set to "application/octet-stream"
@return An instsance of BITHockeyAttachment.
*/
- (instancetype)initWithFilename:(NSString *)filename
hockeyAttachmentData:(NSData *)hockeyAttachmentData
contentType:(NSString *)contentType;
@end

View File

@@ -0,0 +1,75 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2014 HockeyApp, Bit Stadium GmbH.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import "BITHockeyAttachment.h"
@implementation BITHockeyAttachment
- (instancetype)initWithFilename:(NSString *)filename
hockeyAttachmentData:(NSData *)hockeyAttachmentData
contentType:(NSString *)contentType
{
if (self = [super init]) {
_filename = filename;
_hockeyAttachmentData = hockeyAttachmentData;
if (contentType) {
_contentType = contentType;
} else {
_contentType = @"application/octet-stream";
}
}
return self;
}
#pragma mark - NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
if (self.filename) {
[encoder encodeObject:self.filename forKey:@"filename"];
}
if (self.hockeyAttachmentData) {
[encoder encodeObject:self.hockeyAttachmentData forKey:@"data"];
}
[encoder encodeObject:self.contentType forKey:@"contentType"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
_filename = [decoder decodeObjectForKey:@"filename"];
_hockeyAttachmentData = [decoder decodeObjectForKey:@"data"];
_contentType = [decoder decodeObjectForKey:@"contentType"];
}
return self;
}
@end

View File

@@ -35,6 +35,10 @@
#import "BITHockeyManager.h"
#import "BITHockeyManagerDelegate.h"
#if HOCKEYSDK_FEATURE_CRASH_REPORTER || HOCKEYSDK_FEATURE_FEEDBACK
#import "BITHockeyAttachment.h"
#endif
#if HOCKEYSDK_FEATURE_CRASH_REPORTER
#import "BITCrashManager.h"
#import "BITCrashAttachment.h"

View File

@@ -131,6 +131,8 @@
1EAF20AA162DC0F600957B1D /* feedbackActiviy.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EAF20A6162DC0F600957B1D /* feedbackActiviy.png */; };
1EAF20AB162DC0F600957B1D /* feedbackActiviy@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EAF20A7162DC0F600957B1D /* feedbackActiviy@2x.png */; };
1EB52FD5167B766100C801D5 /* HockeySDK.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1E59555F15B6F80E00A03429 /* HockeySDK.strings */; };
1EB92E731955C38C0093C8B6 /* BITHockeyAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EB92E711955C38C0093C8B6 /* BITHockeyAttachment.h */; };
1EB92E741955C38C0093C8B6 /* BITHockeyAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EB92E721955C38C0093C8B6 /* BITHockeyAttachment.m */; };
1ECA8F4D192B5BD8006B9416 /* BITCrashDetailsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ECA8F4B192B5BD8006B9416 /* BITCrashDetailsPrivate.h */; };
1ECA8F51192B6954006B9416 /* BITCrashMetaData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ECA8F4F192B6954006B9416 /* BITCrashMetaData.h */; settings = {ATTRIBUTES = (Public, ); }; };
1ECA8F52192B6954006B9416 /* BITCrashMetaData.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ECA8F50192B6954006B9416 /* BITCrashMetaData.m */; };
@@ -325,6 +327,8 @@
1EAF20A6162DC0F600957B1D /* feedbackActiviy.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = feedbackActiviy.png; sourceTree = "<group>"; };
1EAF20A7162DC0F600957B1D /* feedbackActiviy@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "feedbackActiviy@2x.png"; sourceTree = "<group>"; };
1EB52FC3167B73D400C801D5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/HockeySDK.strings; sourceTree = "<group>"; };
1EB92E711955C38C0093C8B6 /* BITHockeyAttachment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITHockeyAttachment.h; sourceTree = "<group>"; };
1EB92E721955C38C0093C8B6 /* BITHockeyAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITHockeyAttachment.m; sourceTree = "<group>"; };
1ECA8F4B192B5BD8006B9416 /* BITCrashDetailsPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITCrashDetailsPrivate.h; sourceTree = "<group>"; };
1ECA8F4F192B6954006B9416 /* BITCrashMetaData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITCrashMetaData.h; sourceTree = "<group>"; };
1ECA8F50192B6954006B9416 /* BITCrashMetaData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashMetaData.m; sourceTree = "<group>"; };
@@ -522,6 +526,8 @@
1E49A4AA161222B900463151 /* BITStoreButton.m */,
1E49A4AB161222B900463151 /* BITWebTableViewCell.h */,
1E49A4AC161222B900463151 /* BITWebTableViewCell.m */,
1EB92E711955C38C0093C8B6 /* BITHockeyAttachment.h */,
1EB92E721955C38C0093C8B6 /* BITHockeyAttachment.m */,
);
name = Helper;
sourceTree = "<group>";
@@ -780,6 +786,7 @@
1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */,
1EF95CA6162CB037000AE3AD /* BITFeedbackActivity.h in Headers */,
1E49A4B8161222B900463151 /* BITHockeyBaseViewController.h in Headers */,
1EB92E731955C38C0093C8B6 /* BITHockeyAttachment.h in Headers */,
1E49A4AF161222B900463151 /* BITHockeyBaseManager.h in Headers */,
1E0829001708F69A0073050E /* BITStoreUpdateManagerDelegate.h in Headers */,
1E49A4421612223B00463151 /* BITFeedbackListViewCell.h in Headers */,
@@ -1044,6 +1051,7 @@
1E49A4CD161222B900463151 /* BITStoreButton.m in Sources */,
1E90FD7418EDB86400CF0417 /* BITCrashDetails.m in Sources */,
1E49A4D3161222B900463151 /* BITWebTableViewCell.m in Sources */,
1EB92E741955C38C0093C8B6 /* BITHockeyAttachment.m in Sources */,
E48A3DED17B3ED1C00924C3D /* BITAuthenticator.m in Sources */,
1E49A4DB161222D400463151 /* HockeySDKPrivate.m in Sources */,
1E754E5D1621FBB70070AB92 /* BITCrashManager.m in Sources */,

View File

@@ -99,15 +99,15 @@
NSData *data = [[NSData alloc] initWithBase64Encoding:@"TestData"];
NSString* type = @"text/plain";
BITCrashAttachment *originalAttachment = [[BITCrashAttachment alloc] initWithFilename:filename crashAttachmentData:data contentType:type];
BITHockeyAttachment *originalAttachment = [[BITHockeyAttachment alloc] initWithFilename:filename hockeyAttachmentData:data contentType:type];
NSString *attachmentFilename = [[_sut crashesDir] stringByAppendingPathComponent:@"testAttachment"];
[_sut persistAttachment:originalAttachment withFilename:attachmentFilename];
BITCrashAttachment *decodedAttachment = [_sut attachmentForCrashReport:attachmentFilename];
BITHockeyAttachment *decodedAttachment = [_sut attachmentForCrashReport:attachmentFilename];
assertThat(decodedAttachment.filename, equalTo(filename));
assertThat(decodedAttachment.crashAttachmentData, equalTo(data));
assertThat(decodedAttachment.hockeyAttachmentData, equalTo(data));
assertThat(decodedAttachment.contentType, equalTo(type));
}