Merge branch 'master' into develop

# Conflicts:
#	Classes/BITMetricsManager.m
#	Classes/BITPersistence.m
#	HockeySDK-Source.podspec
#	HockeySDK.podspec
#	README.md
#	Support/buildnumber.xcconfig
#	docs/Changelog-template.md
#	docs/Guide-Installation-Setup-template.md
This commit is contained in:
Lukas Spieß
2016-05-06 03:27:49 +02:00
12 changed files with 242 additions and 45 deletions

View File

@@ -234,7 +234,7 @@ typedef void (^BITLatestImageFetchCompletionBlock)(UIImage *_Nonnull latestImage
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
NSArray *preparedItems = self.feedbackComposerPreparedItems;
NSArray *preparedItems = self.feedbackComposerPreparedItems ?: [NSArray array];
#pragma clang diagnostic pop
if ([self.delegate respondsToSelector:@selector(preparedItemsForFeedbackManager:)]) {
preparedItems = [preparedItems arrayByAddingObjectsFromArray:[self.delegate preparedItemsForFeedbackManager:self]];

View File

@@ -32,6 +32,8 @@
@interface BITHockeyHelper : NSObject
FOUNDATION_EXPORT NSString *const kBITExcludeApplicationSupportFromBackup;
+ (BOOL)isURLSessionSupported;
@end
@@ -41,6 +43,9 @@ NSString *bit_settingsDir(void);
BOOL bit_validateEmail(NSString *email);
NSString *bit_keychainHockeySDKServiceName(void);
/* Fix bug where Application Support was excluded from backup. */
void bit_fixBackupAttributeForURL(NSURL *directoryURL);
NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB);
NSString *bit_mainBundleIdentifier(void);
NSString *bit_encodeAppIdentifier(NSString *inputString);
@@ -97,4 +102,5 @@ UIImage *bit_imageWithContentsOfResolutionIndependentFile(NSString * path);
UIImage *bit_imageNamed(NSString *imageName, NSString *bundleName);
UIImage *bit_screenshot(void);
UIImage *bit_appIcon(void);
#endif

View File

@@ -38,6 +38,7 @@
#import <sys/sysctl.h>
static NSString *const kBITUtcDateFormatter = @"utcDateFormatter";
NSString *const kBITExcludeApplicationSupportFromBackup = @"kBITExcludeApplicationSupportFromBackup";
@implementation BITHockeyHelper
@@ -141,6 +142,28 @@ NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB) {
return result;
}
#pragma mark Exclude from backup fix
void bit_fixBackupAttributeForURL(NSURL *directoryURL) {
BOOL shouldExcludeAppSupportDirFromBackup = [[NSUserDefaults standardUserDefaults] boolForKey:kBITExcludeApplicationSupportFromBackup];
if (shouldExcludeAppSupportDirFromBackup) {
return;
}
if (directoryURL) {
NSError *getResourceError = nil;
NSNumber *appSupportDirExcludedValue;
if ([directoryURL getResourceValue:&appSupportDirExcludedValue forKey:NSURLIsExcludedFromBackupKey error:&getResourceError] && appSupportDirExcludedValue) {
NSError *setResourceError = nil;
[directoryURL setResourceValue:@NO forKey:NSURLIsExcludedFromBackupKey error:&setResourceError];
}
}
}
#pragma mark Identifiers
NSString *bit_mainBundleIdentifier(void) {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
}
@@ -246,6 +269,8 @@ NSString *bit_appAnonID(BOOL forceNewAnonID) {
return appAnonID;
}
#pragma mark Environment detection
BOOL bit_isPreiOS7Environment(void) {
static BOOL isPreiOS7Environment = YES;
static dispatch_once_t checkOS;

View File

@@ -228,6 +228,11 @@ bitstadium_info_t bitstadium_library_info __attribute__((section("__TEXT,__bit_h
return;
}
// Fix bug where Application Support directory was encluded from backup
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
bit_fixBackupAttributeForURL(appSupportURL);
if (![self isSetUpOnMainThread]) return;
if ((self.appEnvironment == BITEnvironmentAppStore) && [self isInstallTrackingDisabled]) {

View File

@@ -13,8 +13,10 @@ static NSString *const kBITTelemetry = @"Telemetry";
static NSString *const kBITMetaData = @"MetaData";
static NSString *const kBITFileBaseString = @"hockey-app-bundle-";
static NSString *const kBITFileBaseStringMeta = @"metadata";
static NSString *const kBITTelemetryDirectoryPath = @"com.microsoft.HockeyApp/Telemetry/";
static NSString *const kBITMetaDataDirectoryPath = @"com.microsoft.HockeyApp/MetaData/";
static NSString *const kBITHockeyDirectory = @"com.microsoft.HockeyApp";
static NSString *const kBITTelemetryDirectory = @"Telemetry";
static NSString *const kBITMetaDataDirectory = @"MetaData";
static char const *kBITPersistenceQueueString = "com.microsoft.HockeyApp.persistenceQueue";
static NSUInteger const BITDefaultFileCount = 50;
@@ -158,8 +160,6 @@ static NSUInteger const BITDefaultFileCount = 50;
#pragma mark - Private
- (NSString *)fileURLForType:(BITPersistenceType)type {
NSArray<NSString *> *searchPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportPath = searchPaths.lastObject;
NSString *fileName = nil;
NSString *filePath;
@@ -167,13 +167,13 @@ static NSUInteger const BITDefaultFileCount = 50;
switch (type) {
case BITPersistenceTypeMetaData: {
fileName = kBITFileBaseStringMeta;
filePath = [appSupportPath stringByAppendingPathComponent:kBITMetaDataDirectoryPath];
filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITMetaDataDirectory];
break;
};
default: {
NSString *uuid = bit_UUID();
fileName = [NSString stringWithFormat:@"%@%@", kBITFileBaseString, uuid];
filePath = [appSupportPath stringByAppendingPathComponent:kBITTelemetryDirectoryPath];
filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITTelemetryDirectory];
break;
};
}
@@ -187,39 +187,46 @@ static NSUInteger const BITDefaultFileCount = 50;
* Create directory structure if necessary and exclude it from iCloud backup
*/
- (void)createDirectoryStructureIfNeeded {
//Application Support Dir
NSURL *appURL = [NSURL fileURLWithPath:self.appHockeySDKDirectoryPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
if (appSupportURL) {
if (appURL) {
NSError *error = nil;
//App Support and Telemetry Directory
NSURL *folderURL = [appSupportURL URLByAppendingPathComponent:kBITTelemetryDirectoryPath];
// Create HockeySDK folder if needed
if (![fileManager createDirectoryAtURL:appURL withIntermediateDirectories:YES attributes:nil error:&error]) {
BITHockeyLogError(@"ERROR: %@", error.localizedDescription);
return;
}
// Create metadata subfolder
NSURL *metaDataURL = [appURL URLByAppendingPathComponent:kBITMetaDataDirectory];
if (![fileManager createDirectoryAtURL:metaDataURL withIntermediateDirectories:YES attributes:nil error:&error]) {
BITHockeyLogError(@"ERROR: %@", error.localizedDescription);
return;
}
// Create telemetry subfolder
//NOTE: createDirectoryAtURL:withIntermediateDirectories:attributes:error
//will return YES if the directory already exists and won't override anything.
//No need to check if the directory already exists.
if (![fileManager createDirectoryAtURL:folderURL withIntermediateDirectories:YES attributes:nil error:&error]) {
NSURL *telemetryURL = [appURL URLByAppendingPathComponent:kBITTelemetryDirectory];
if (![fileManager createDirectoryAtURL:telemetryURL withIntermediateDirectories:YES attributes:nil error:&error]) {
BITHockeyLogError(@"ERROR: %@", error.localizedDescription);
return; //TODO we can't use persistence at all in this case, what do we want to do now? Notify the user?
return;
}
//MetaData Directory
folderURL = [appSupportURL URLByAppendingPathComponent:kBITMetaDataDirectoryPath];
if (![fileManager createDirectoryAtURL:folderURL withIntermediateDirectories:NO attributes:nil error:&error]) {
BITHockeyLogError(@"ERROR: %@", error.localizedDescription);
return; //TODO we can't use persistence at all in this case, what do we want to do now? Notify the user?
//Exclude HockeySDK folder from backup
if (![appURL setResourceValue:@YES
forKey:NSURLIsExcludedFromBackupKey
error:&error]) {
BITHockeyLogError(@"ERROR: Error excluding %@ from backup %@", appURL.lastPathComponent, error.localizedDescription);
} else {
BITHockeyLogDebug(@"INFO: Exclude %@ from backup", appURL);
}
_directorySetupComplete = YES;
//Exclude from Backup
if (![appSupportURL setResourceValue:@YES
forKey:NSURLIsExcludedFromBackupKey
error:&error]) {
BITHockeyLogError(@"Error excluding %@ from backup %@", appSupportURL.lastPathComponent, error.localizedDescription);
}
else {
BITHockeyLogDebug(@"INFO: Excluding %@ from backup", appSupportURL);
}
}
}
@@ -250,21 +257,18 @@ static NSUInteger const BITDefaultFileCount = 50;
}
- (NSString *)folderPathForType:(BITPersistenceType)type {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
NSString *subFolder = @"";
switch (type) {
case BITPersistenceTypeTelemetry: {
subFolder = kBITTelemetryDirectoryPath;
subFolder = kBITTelemetryDirectory;
break;
}
case BITPersistenceTypeMetaData: {
subFolder = kBITMetaDataDirectoryPath;
subFolder = kBITMetaDataDirectory;
break;
}
}
path = [path stringByAppendingPathComponent:subFolder];
return path;
return [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:subFolder];
}
/**
@@ -279,6 +283,16 @@ static NSUInteger const BITDefaultFileCount = 50;
});
}
- (NSString *)appHockeySDKDirectoryPath {
if (!_appHockeySDKDirectoryPath) {
NSString *appSupportPath = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject] stringByStandardizingPath];
if (appSupportPath) {
_appHockeySDKDirectoryPath = [appSupportPath stringByAppendingPathComponent:kBITHockeyDirectory];
}
}
return _appHockeySDKDirectoryPath;
}
@end
#endif /* HOCKEYSDK_FEATURE_METRICS */

View File

@@ -37,6 +37,8 @@ FOUNDATION_EXPORT NSString *const BITPersistenceSuccessNotification;
*/
@property (nonatomic, assign) NSUInteger maxFileCount;
@property (nonatomic, strong) NSString *appHockeySDKDirectoryPath;
/**
* An array with all file paths, that have been requested by the sender. If the sender
* triggers a delete, the appropriate path should also be removed here. We keep to
@@ -134,6 +136,7 @@ FOUNDATION_EXPORT NSString *const BITPersistenceSuccessNotification;
*/
- (NSString *)fileURLForType:(BITPersistenceType)type;
- (void)createDirectoryStructureIfNeeded;
#endif /* HOCKEYSDK_FEATURE_METRICS */

View File

@@ -6,6 +6,10 @@
- [Changelog](http://www.hockeyapp.net/help/sdk/ios/4.1.0-beta.1/docs/docs/Changelog.html)
NOTE: With the release of HockeySDK 4.0.0-alpha.1 a bug was introduced which lead to the exclusion of the Application Support folder from iCloud and iTunes backups.
If you have been using one of the affected versions (4.0.0-alpha.2, Version 4.0.0-beta.1, 4.0.0, 4.1.0-alpha.1, 4.1.0-alpha.2, or Version 4.1.0-beta.1), please make sure to update to at least version 4.0.1 or 4.1.0-beta.2 of our SDK as soon as you can.
## Introduction
HockeySDK-iOS implements support for using HockeyApp in your iOS applications.

View File

@@ -236,24 +236,35 @@
self.sut.feedbackComposeHideImageAttachmentButton = YES;
XCTAssertTrue(self.sut.feedbackComposeHideImageAttachmentButton);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
self.sut.feedbackComposerPreparedItems = @[sampleImage1, sampleData1];
#pragma clang diagnostic pop
id<BITFeedbackManagerDelegate> mockDelegate = mockProtocol(@protocol(BITFeedbackManagerDelegate));
[given([mockDelegate preparedItemsForFeedbackManager:self.sut]) willReturn:@[sampleImage2, sampleData2]];
self.sut.delegate = mockDelegate;
// Test when feedbackComposerPreparedItems is also set
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
self.sut.feedbackComposerPreparedItems = @[sampleImage1, sampleData1];
#pragma clang diagnostic pop
BITFeedbackComposeViewController *composeViewController = [self.sut feedbackComposeViewController];
NSArray *attachments = [composeViewController performSelector:@selector(attachments)];
XCTAssertEqual(attachments.count, 4);
// Test when feedbackComposerPreparedItems is nil
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
self.sut.feedbackComposerPreparedItems = nil;
#pragma clang diagnostic pop
composeViewController = [self.sut feedbackComposeViewController];
attachments = [composeViewController performSelector:@selector(attachments)];
XCTAssertEqual(attachments.count, 2);
XCTAssertTrue(composeViewController.hideImageAttachmentButton);
XCTAssertEqual(composeViewController.delegate, mockDelegate);
}

View File

@@ -31,6 +31,7 @@
- (void)tearDown {
// Tear-down code here.
[super tearDown];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kBITExcludeApplicationSupportFromBackup];
}
- (void)testURLEncodedString {
@@ -229,4 +230,62 @@
assertThat(result, nilValue());
}
- (void)testBackupFixRemovesExcludeAttribute {
// Setup: Attribute is set and NSUSerDefaults DON'T contain kBITExcludeApplicationSupportFromBackup == YES
NSURL *testAppSupportURL = [self createBackupExcludedTestDirectoryForURL];
XCTAssertNotNil(testAppSupportURL);
XCTAssertTrue([self excludeAttributeIsSetForURL:testAppSupportURL]);
// Test
bit_fixBackupAttributeForURL(testAppSupportURL);
// Verify
XCTAssertFalse([self excludeAttributeIsSetForURL:testAppSupportURL]);
}
- (void)testBackupFixIgnoresRemovalOfExcludeAttributeUserDefaultsContainKey {
// Setup: Attribute is set and NSUSerDefaults DO contain kBITExcludeApplicationSupportFromBackup == YES
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITExcludeApplicationSupportFromBackup];
NSURL *testAppSupportURL = [self createBackupExcludedTestDirectoryForURL];
XCTAssertNotNil(testAppSupportURL);
XCTAssertTrue([self excludeAttributeIsSetForURL:testAppSupportURL]);
// Test
bit_fixBackupAttributeForURL(testAppSupportURL);
// Verify
XCTAssertTrue([self excludeAttributeIsSetForURL:testAppSupportURL]);
}
#pragma mark - Test Helper
- (NSURL *)createBackupExcludedTestDirectoryForURL{
NSString *testDirectory = @"HockeyTest";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *testAppSupportURL = [[[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:testDirectory];
if ([fileManager createDirectoryAtURL:testAppSupportURL withIntermediateDirectories:YES attributes:nil error:nil]) {
if ([testAppSupportURL setResourceValue:@YES
forKey:NSURLIsExcludedFromBackupKey
error:nil]) {
return testAppSupportURL;
}
}
return nil;
}
- (BOOL)excludeAttributeIsSetForURL:(NSURL *)directoryURL {
NSError *getResourceError = nil;
NSNumber *appSupportDirExcludedValue;
if ([directoryURL getResourceValue:&appSupportDirExcludedValue forKey:NSURLIsExcludedFromBackupKey error:&getResourceError] && appSupportDirExcludedValue) {
if ([appSupportDirExcludedValue isEqualToValue:@YES]) {
return YES;
}
}
return NO;
}
@end

View File

@@ -45,6 +45,55 @@
[self.mockNotificationCenter removeObserver:observerMock];
}
- (void)testCreateDirectoryStructureIfNeeded {
// Setup
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *appSupportPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = self.sut.appHockeySDKDirectoryPath;
NSError *fileRemovalError = nil;
[fileManager removeItemAtPath:path error:&fileRemovalError];
NSError *getResourceError = nil;
NSNumber *resourveValue = nil;
XCTAssertTrue([[NSURL fileURLWithPath:appSupportPath] setResourceValue:@NO
forKey:NSURLIsExcludedFromBackupKey
error:&getResourceError]);
// Assert
XCTAssertNil(fileRemovalError);
XCTAssertFalse([fileManager fileExistsAtPath:path]);
// Act
[self.sut createDirectoryStructureIfNeeded];
// Verify
BOOL isDirectory = NO;
[fileManager fileExistsAtPath:path isDirectory:&isDirectory];
XCTAssertTrue(isDirectory);
// Flag stays @NO on Application Support directory
getResourceError = nil;
resourveValue = nil;
[[NSURL fileURLWithPath:appSupportPath] getResourceValue:&resourveValue
forKey:NSURLIsExcludedFromBackupKey
error:&getResourceError];
XCTAssertNil(getResourceError);
XCTAssertEqual(resourveValue, @NO);
// Flag is set to @YES on our custom subdirectory
getResourceError = nil;
resourveValue = nil;
[[NSURL fileURLWithPath:path] getResourceValue:&resourveValue
forKey:NSURLIsExcludedFromBackupKey
error:&getResourceError];
XCTAssertNil(getResourceError);
XCTAssertEqual(resourveValue, @YES);
// TODO: Check subdirectories have been created
}
- (void)testFolderPathForType {
NSString *path = [self.sut folderPathForType:BITPersistenceTypeTelemetry];
XCTAssertFalse([path rangeOfString:@"com.microsoft.HockeyApp/Telemetry"].location == NSNotFound);

View File

@@ -24,6 +24,23 @@
- [IMPROVEMENT] Reuse `NSURLSession` object
- [IMPROVEMENT] Under the hood improvements and cleanup
## Version 4.0.1
- [BUGFIX] Fixes an issue where the whole app's Application Support directory was accidentally excluded from backups.
This SDK release explicitly includes the Application Support directory into backups. If you want to opt-out of this fix and keep the Application Directory's backup flag untouched, add the following line above the SDK setup code:
- Objective-C:
```objectivec
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"BITExcludeApplicationSupportFromBackup"];
```
- Swift:
```swift
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "BITExcludeApplicationSupportFromBackup")
```
- [BUGFIX] Fixes an issue that prevented preparedItemsForFeedbackManager: delegate method from working
## Version 4.0.0
- [NEW] Added official Carthage support

View File

@@ -2,6 +2,10 @@
- [Changelog](http://www.hockeyapp.net/help/sdk/ios/4.1.0-beta.1/docs/docs/Changelog.html)
**NOTE:** With the release of HockeySDK 4.0.0-alpha.1 a bug was introduced which lead to the exclusion of the Application Support folder from iCloud and iTunes backups.
If you have been using one of the affected versions (4.0.0-alpha.2, Version 4.0.0-beta.1, 4.0.0, 4.1.0-alpha.1, 4.1.0-alpha.2, or Version 4.1.0-beta.1), please make sure to update to at least version 4.0.1 or 4.1.0-beta.2 of our SDK as soon as you can.
## Introduction
This document contains the following sections: