mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00

git-subtree-dir: submodules/HockeySDK-iOS git-subtree-mainline: 085acd26c4432939403765234266e3c1be0f3dd9 git-subtree-split: c7d0c7026303253e2ac576c02655691e5d314fe2
306 lines
10 KiB
Objective-C
306 lines
10 KiB
Objective-C
#import "HockeySDK.h"
|
|
|
|
#if HOCKEYSDK_FEATURE_METRICS
|
|
|
|
#import "BITPersistence.h"
|
|
#import "BITPersistencePrivate.h"
|
|
#import "HockeySDKPrivate.h"
|
|
#import "BITHockeyHelper.h"
|
|
|
|
NSString *const BITPersistenceSuccessNotification = @"BITHockeyPersistenceSuccessNotification";
|
|
|
|
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 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;
|
|
|
|
@interface BITPersistence ()
|
|
|
|
@property (nonatomic) BOOL directorySetupComplete;
|
|
|
|
@end
|
|
|
|
@implementation BITPersistence
|
|
|
|
#pragma mark - Public
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_persistenceQueue = dispatch_queue_create(kBITPersistenceQueueString, DISPATCH_QUEUE_SERIAL); //TODO several queues?
|
|
_requestedBundlePaths = [NSMutableArray new];
|
|
_maxFileCount = BITDefaultFileCount;
|
|
|
|
// Evantually, there will be old files on disk, the flag will be updated before the first event gets created
|
|
_directorySetupComplete = NO; //will be set to true in createDirectoryStructureIfNeeded
|
|
|
|
[self createDirectoryStructureIfNeeded];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* Saves the Bundle using NSKeyedArchiver and NSData's writeToFile:atomically
|
|
* Sends out a BITHockeyPersistenceSuccessNotification in case of success
|
|
*/
|
|
- (void)persistBundle:(NSData *)bundle {
|
|
//TODO send out a fail notification?
|
|
NSString *fileURL = [self fileURLForType:BITPersistenceTypeTelemetry];
|
|
|
|
if (bundle) {
|
|
__weak typeof(self) weakSelf = self;
|
|
dispatch_async(self.persistenceQueue, ^{
|
|
typeof(self) strongSelf = weakSelf;
|
|
BOOL success = [bundle writeToFile:fileURL atomically:YES];
|
|
if (success) {
|
|
BITHockeyLogDebug(@"INFO: Wrote bundle to %@", fileURL);
|
|
[strongSelf sendBundleSavedNotification];
|
|
}
|
|
else {
|
|
BITHockeyLogError(@"Error writing bundle to %@", fileURL);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
BITHockeyLogWarning(@"WARNING: Unable to write %@ as provided bundle was null", fileURL);
|
|
}
|
|
}
|
|
|
|
- (void)persistMetaData:(NSDictionary *)metaData {
|
|
NSString *fileURL = [self fileURLForType:BITPersistenceTypeMetaData];
|
|
//TODO send out a notification, too?!
|
|
dispatch_async(self.persistenceQueue, ^{
|
|
[NSKeyedArchiver archiveRootObject:metaData toFile:fileURL];
|
|
});
|
|
}
|
|
|
|
- (BOOL)isFreeSpaceAvailable {
|
|
NSArray *files = [self persistedFilesForType:BITPersistenceTypeTelemetry];
|
|
return files.count < self.maxFileCount;
|
|
}
|
|
|
|
- (NSString *)requestNextFilePath {
|
|
__block NSString *path = nil;
|
|
__weak typeof(self) weakSelf = self;
|
|
dispatch_sync(self.persistenceQueue, ^() {
|
|
typeof(self) strongSelf = weakSelf;
|
|
|
|
path = [strongSelf nextURLOfType:BITPersistenceTypeTelemetry];
|
|
|
|
if (path) {
|
|
[self.requestedBundlePaths addObject:path];
|
|
}
|
|
});
|
|
return path;
|
|
}
|
|
|
|
- (NSDictionary *)metaData {
|
|
NSString *filePath = [self fileURLForType:BITPersistenceTypeMetaData];
|
|
NSObject *bundle = [self bundleAtFilePath:filePath withFileBaseString:kBITFileBaseStringMeta];
|
|
if ([bundle isKindOfClass:NSDictionary.class]) {
|
|
return (NSDictionary *) bundle;
|
|
}
|
|
BITHockeyLogDebug(@"INFO: The context meta data file could not be loaded.");
|
|
return [NSDictionary dictionary];
|
|
}
|
|
|
|
- (NSObject *)bundleAtFilePath:(NSString *)filePath withFileBaseString:(NSString *)filebaseString {
|
|
id bundle = nil;
|
|
if (filePath && [filePath rangeOfString:filebaseString].location != NSNotFound) {
|
|
bundle = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
|
|
}
|
|
return bundle;
|
|
}
|
|
|
|
- (NSData *)dataAtFilePath:(NSString *)path {
|
|
NSData *data = nil;
|
|
if (path && [path rangeOfString:kBITFileBaseString].location != NSNotFound) {
|
|
data = [NSData dataWithContentsOfFile:path];
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Deletes a file at the given path.
|
|
*
|
|
* @param path The path to look for a file and delete it.
|
|
*/
|
|
- (void)deleteFileAtPath:(NSString *)path {
|
|
__weak typeof(self) weakSelf = self;
|
|
dispatch_sync(self.persistenceQueue, ^() {
|
|
typeof(self) strongSelf = weakSelf;
|
|
if ([path rangeOfString:kBITFileBaseString].location != NSNotFound) {
|
|
NSError *error = nil;
|
|
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
|
|
BITHockeyLogError(@"Error deleting file at path %@", path);
|
|
}
|
|
else {
|
|
BITHockeyLogDebug(@"INFO: Successfully deleted file at path %@", path);
|
|
[strongSelf.requestedBundlePaths removeObject:path];
|
|
}
|
|
} else {
|
|
BITHockeyLogDebug(@"INFO: Empty path, nothing to delete");
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
- (void)giveBackRequestedFilePath:(NSString *)filePath {
|
|
__weak typeof(self) weakSelf = self;
|
|
dispatch_async(self.persistenceQueue, ^() {
|
|
typeof(self) strongSelf = weakSelf;
|
|
|
|
[strongSelf.requestedBundlePaths removeObject:filePath];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Private
|
|
|
|
- (nullable NSString *)fileURLForType:(BITPersistenceType)type {
|
|
|
|
NSString *fileName = nil;
|
|
NSString *filePath;
|
|
|
|
switch (type) {
|
|
case BITPersistenceTypeMetaData: {
|
|
fileName = kBITFileBaseStringMeta;
|
|
filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITMetaDataDirectory];
|
|
break;
|
|
};
|
|
case BITPersistenceTypeTelemetry: {
|
|
NSString *uuid = bit_UUID();
|
|
fileName = [NSString stringWithFormat:@"%@%@", kBITFileBaseString, uuid];
|
|
filePath = [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:kBITTelemetryDirectory];
|
|
break;
|
|
};
|
|
}
|
|
|
|
filePath = [filePath stringByAppendingPathComponent:fileName];
|
|
|
|
return filePath;
|
|
}
|
|
|
|
/**
|
|
* Create directory structure if necessary and exclude it from iCloud backup
|
|
*/
|
|
- (void)createDirectoryStructureIfNeeded {
|
|
// Using the local variable looks unnecessary but it actually silences a static analyzer warning.
|
|
NSString *appHockeySDKDirectoryPath = [self appHockeySDKDirectoryPath];
|
|
NSURL *appURL = [NSURL fileURLWithPath:appHockeySDKDirectoryPath];
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
if (appURL) {
|
|
NSError *error = nil;
|
|
|
|
// 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.
|
|
NSURL *telemetryURL = [appURL URLByAppendingPathComponent:kBITTelemetryDirectory];
|
|
if (![fileManager createDirectoryAtURL:telemetryURL withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
BITHockeyLogError(@"ERROR: %@", error.localizedDescription);
|
|
return;
|
|
}
|
|
|
|
//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);
|
|
}
|
|
|
|
self.directorySetupComplete = YES;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns the URL to the next file depending on the specified type. If there's no file, return nil.
|
|
*/
|
|
- (NSString *)nextURLOfType:(BITPersistenceType)type {
|
|
NSArray<NSURL *> *fileNames = [self persistedFilesForType:type];
|
|
if (fileNames && fileNames.count > 0) {
|
|
for (NSURL *filename in fileNames) {
|
|
NSString *absolutePath = filename.path;
|
|
if (![self.requestedBundlePaths containsObject:absolutePath]) {
|
|
return absolutePath;
|
|
}
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSArray *)persistedFilesForType: (BITPersistenceType)type {
|
|
NSString *directoryPath = [self folderPathForType:type];
|
|
NSError *error = nil;
|
|
NSArray<NSURL *> *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:directoryPath]
|
|
includingPropertiesForKeys:@[NSURLNameKey]
|
|
options:NSDirectoryEnumerationSkipsHiddenFiles
|
|
error:&error];
|
|
return fileNames;
|
|
}
|
|
|
|
- (NSString *)folderPathForType:(BITPersistenceType)type {
|
|
NSString *subFolder = @"";
|
|
switch (type) {
|
|
case BITPersistenceTypeTelemetry: {
|
|
subFolder = kBITTelemetryDirectory;
|
|
break;
|
|
}
|
|
case BITPersistenceTypeMetaData: {
|
|
subFolder = kBITMetaDataDirectory;
|
|
break;
|
|
}
|
|
}
|
|
return [self.appHockeySDKDirectoryPath stringByAppendingPathComponent:subFolder];
|
|
}
|
|
|
|
/**
|
|
* Send a BITHockeyPersistenceSuccessNotification to the main thread to notify observers that we have successfully saved a file
|
|
* This is typically used to trigger sending.
|
|
*/
|
|
- (void)sendBundleSavedNotification {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
BITHockeyLogDebug(@"Sending notification: %@", BITPersistenceSuccessNotification);
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:BITPersistenceSuccessNotification
|
|
object:nil
|
|
userInfo:nil];
|
|
});
|
|
}
|
|
|
|
- (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 */
|
|
|