// // DropboxV2ObjC.m // FMC Planner 2 // // Created by Kilian Hofmann on 29.03.16. // Copyright © 2016 Kilian Hofmann. All rights reserved. // #import "DropboxV2ObjC.h" @implementation DropboxV2ObjC - (DropboxV2ObjC *)init { self = [super init]; self.rootDirectory = @""; self.kJSONNullObject = [@"null" dataUsingEncoding:NSASCIIStringEncoding]; self.kDropboxProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"dropbox" port:443 protocol:@"HTTP" realm:nil authenticationMethod:@"OAuth2"]; NSDictionary *credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.kDropboxProtectionSpace]; NSURLCredential *credential = [credentials.objectEnumerator nextObject]; self.token = credential.password; return self; } #pragma mark - OAuth stuff - (BOOL)authorizeUserWithToke:(NSURL *)token completion:(void (^)(void))handler presenter:(UIViewController *)presenter { NSScanner *scan = [NSScanner scannerWithString:token.absoluteString]; NSString *error = [[NSString alloc] init]; [scan scanUpToString:@"&error=" intoString:&error]; if (![error isEqualToString:token.absoluteString]) { handler(); return false; } // No error, refine token NSString *tokenUnrefined = [[NSString alloc] init]; scan = [NSScanner scannerWithString:token.absoluteString]; [scan scanUpToString:@"&" intoString:&tokenUnrefined]; NSString *tokenRefined = [tokenUnrefined substringFromIndex:21]; _token = tokenRefined; NSURLCredential *credential = [NSURLCredential credentialWithUser:@"Dropbox" password:_token persistence:NSURLCredentialPersistencePermanent]; [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:_kDropboxProtectionSpace]; handler(); return true; } - (void)deauthorizeUserWithPresenter:(UIViewController *)presenter { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:@"https://api.dropboxapi.com/1/disable_access_token"]; [request addValue:[NSString stringWithFormat:@"Bearer %@", _token] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Authorization") .precomposedStringWithCanonicalMapping]; [request addValue:(@"application/json") .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Content-Type") .precomposedStringWithCanonicalMapping]; request.HTTPMethod = @"POST"; request.HTTPBody = _kJSONNullObject; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (data != nil && [[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isEqualToDictionary:@{}]) { NSDictionary *credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace: _kDropboxProtectionSpace]; NSURLCredential *credential = [credentials.objectEnumerator nextObject]; [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:_kDropboxProtectionSpace]; } else if (error != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert:error presenter:presenter.navigationController .topViewController]; }]; } else if (![[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isEqualToDictionary:@{}]) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isKindOfClass:[NSDictionary class]]) { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey: DropboxErrorUserInfo] : NSLocalizedDescriptionKey }] presenter:presenter .navigationController .topViewController]; } else { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSString alloc] initWithData:data encoding: NSASCIIStringEncoding] : NSLocalizedDescriptionKey }] presenter:presenter .navigationController .topViewController]; } }]; } }]; } #pragma mark - File and directory operations /** * Pass nil as path for root dir * @param completion executed after completion */ - (void)contentsOfPath:(NSString *)path completion:(void (^)(NSArray *data, BOOL success))handler presenter:(UIViewController *)presenter { if (path == nil) { path = _rootDirectory; } else { path = [NSString stringWithFormat:@"/%@", path]; } NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:@"https://api.dropboxapi.com/2/files/list_folder"]; [request addValue:[NSString stringWithFormat:@"Bearer %@", _token] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Authorization") .precomposedStringWithCanonicalMapping]; [request addValue:(@"application/json") .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Content-Type") .precomposedStringWithCanonicalMapping]; request.HTTPMethod = @"POST"; NSData *data = [NSJSONSerialization dataWithJSONObject:@{ @"path" : path.precomposedStringWithCanonicalMapping } options:0 error:nil]; request.HTTPBody = [self createJSONExcapedHTTPBody:data]; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (data != nil) { data = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; if ([data isKindOfClass:[NSDictionary class]]) { NSArray *data2 = [data valueForKey:@"entries"]; if (data2 == nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isKindOfClass:[NSDictionary class]]) { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain: DropboxErrorDomain code:100 userInfo:@{ [[NSJSONSerialization JSONObjectWithData: data options: 0 error: nil] valueForKey: DropboxErrorUserInfo] : NSLocalizedDescriptionKey }] presenter: presenter .navigationController .topViewController]; } else { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain: DropboxErrorDomain code:100 userInfo:@{ [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding] : NSLocalizedDescriptionKey }] presenter: presenter .navigationController .topViewController]; } }]; handler(nil, NO); } else { handler(data2, YES); } } } else if (error != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert:error presenter:presenter.navigationController .topViewController]; }]; handler(nil, NO); } }]; } /** * Specify if EXPORT or SAVE in file name * IS synchronus URLRequest, best executed in background thread. */ - (void)downloadFromDropbox:(NSArray *)files presenter:(UIViewController *)presenter completion:(void (^)(BOOL success))handler { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:@"https://content.dropboxapi.com/2/files/download"]; [request addValue:[NSString stringWithFormat:@"Bearer %@", _token] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Authorization") .precomposedStringWithCanonicalMapping]; [request addValue:(@"").precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Content-Type")]; request.HTTPMethod = @"POST"; request.HTTPBody = nil; // File Path for (NSString *string in files) { NSData *data2 = [NSJSONSerialization dataWithJSONObject:@{ @"path" : [NSString stringWithFormat:@"/%@", string] .precomposedStringWithCanonicalMapping } options:0 error:nil]; [request setValue:[[NSString alloc] initWithData:[self createJSONExcapedHTTPBody:data2] encoding:NSASCIIStringEncoding] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Dropbox-API-Arg") .precomposedStringWithCanonicalMapping]; NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error == nil) { if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey:@"error_summary"] == nil) { [data writeToFile:[SharedDeclerations savePathForFile:string] atomically:YES]; handler(YES); } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isKindOfClass:[NSDictionary class]]) { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey: DropboxErrorUserInfo] : NSLocalizedDescriptionKey }] presenter:presenter.navigationController .topViewController]; } else { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSString alloc] initWithData:data encoding: NSASCIIStringEncoding] : NSLocalizedDescriptionKey }] presenter:presenter.navigationController .topViewController]; } }]; handler(NO); } } else if (error != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert:error presenter:presenter.navigationController .topViewController]; }]; handler(NO); } } } /** * Specify if SAVE or OLDSAVE in file name * IS synchronus URLRequest, best executed in background thread. */ - (void)uploadToDropbox:(NSArray *)files presenter:(UIViewController *)presenter completion:(void (^)(BOOL success))handler { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:@"https://content.dropboxapi.com/2/files/upload"]; [request addValue:[NSString stringWithFormat:@"Bearer %@", _token] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Authorization") .precomposedStringWithCanonicalMapping]; [request addValue:(@"application/octet-stream") .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Content-Type")]; request.HTTPMethod = @"POST"; // File Path for (NSString *string in files) { NSData *data2 = [NSJSONSerialization dataWithJSONObject:@{ @"path" : [NSString stringWithFormat:@"/%@", string] .precomposedStringWithCanonicalMapping, @"mode" : @"overwrite".precomposedStringWithCanonicalMapping, } options:0 error:nil]; [request setValue:[[NSString alloc] initWithData:[self createJSONExcapedHTTPBody:data2] encoding:NSASCIIStringEncoding] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Dropbox-API-Arg") .precomposedStringWithCanonicalMapping]; request.HTTPBody = [NSData dataWithContentsOfFile:[SharedDeclerations savePathForFile:string]]; NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert:error presenter:presenter.navigationController .topViewController]; }]; handler(NO); } if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey:@"error_summary"] != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] isKindOfClass:[NSDictionary class]]) { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey: DropboxErrorUserInfo] : NSLocalizedDescriptionKey }] presenter:presenter.navigationController .topViewController]; } else { [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSString alloc] initWithData:data encoding: NSASCIIStringEncoding] : NSLocalizedDescriptionKey }] presenter:presenter.navigationController .topViewController]; } }]; handler(NO); } } } - (void)createFolderAtPath:(NSString *)path presenter:(UIViewController *)presenter { if (path == nil) { path = _rootDirectory; } else { path = [NSString stringWithFormat:@"/%@", path]; } NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:@"https://api.dropboxapi.com/2/files/create_folder"]; [request addValue:[NSString stringWithFormat:@"Bearer %@", _token] .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Authorization") .precomposedStringWithCanonicalMapping]; [request addValue:(@"application/json") .precomposedStringWithCanonicalMapping forHTTPHeaderField:(@"Content-Type") .precomposedStringWithCanonicalMapping]; request.HTTPMethod = @"POST"; NSData *data = [NSJSONSerialization dataWithJSONObject:@{ @"path" : path.precomposedStringWithCanonicalMapping } options:0 error:nil]; request.HTTPBody = [self createJSONExcapedHTTPBody:data]; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (error == nil) { if ([[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey:@"error_summary"] == nil) { } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert: [NSError errorWithDomain:DropboxErrorDomain code:100 userInfo:@{ [[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] valueForKey: DropboxErrorUserInfo] : NSLocalizedDescriptionKey }] presenter:presenter .navigationController .topViewController]; }]; } } else if (error != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [SharedDeclerations presentErrorAlert:error presenter:presenter.navigationController .topViewController]; }]; } }]; } #pragma mark - Setup methods - (void)setupDropboxWithHandler:(void (^)(void))handler presenter:(UIViewController *)presenter { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ // Exports (PMDG format) [self contentsOfPath:nil completion:^(NSArray *data, BOOL success) { BOOL exports = false; for (NSDictionary *dict in data) { if ([[dict valueForKey:@"name"] isEqualToString:@"EXPORTS"]) { exports = true; break; } } if (!exports) { [self createFolderAtPath:@"EXPORTS" presenter:presenter.navigationController .topViewController]; } } presenter:presenter]; // NavData (3 files from QW Navigraph data [self contentsOfPath:nil completion:^(NSArray *data, BOOL success) { BOOL exports = false; for (NSDictionary *dict in data) { if ([[dict valueForKey:@"name"] isEqualToString:@"NAVDATA"]) { exports = true; break; } } if (!exports) { [self createFolderAtPath:@"NAVDATA" presenter:presenter.navigationController .topViewController]; } } presenter:presenter.navigationController.topViewController]; // Backups of Saves [self contentsOfPath:nil completion:^(NSArray *data, BOOL success) { BOOL exports = false; for (NSDictionary *dict in data) { if ([[dict valueForKey:@"name"] isEqualToString:@"SAVES"]) { exports = true; break; } } if (!exports) { [self createFolderAtPath:@"SAVES" presenter:presenter.navigationController .topViewController]; } } presenter:presenter.navigationController.topViewController]; // Old saves (FMC Planner + (or X?)) [self contentsOfPath:nil completion:^(NSArray *data, BOOL success) { BOOL exports = false; for (NSDictionary *dict in data) { if ([[dict valueForKey:@"name"] isEqualToString:@"OLDSAVES"]) { exports = true; break; } } if (!exports) { [self createFolderAtPath:@"OLDSAVES" presenter:presenter.navigationController .topViewController]; } } presenter:presenter.navigationController.topViewController]; }]; handler(); } #pragma mark - Helper methods - (id)parseJSON:(NSData *)data { NSError *error = nil; id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (error == nil) { return object; } else { return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; } } - (NSData *)createJSONExcapedHTTPBody:(NSData *)data { NSString *temp = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; temp = [temp stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; return [temp dataUsingEncoding:NSASCIIStringEncoding]; } @end