1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-12 09:29:55 +01:00
Telegram/thirdparty/GDFileManager/GDGoogleDriveClient.m
2015-10-01 19:19:52 +03:00

589 lines
29 KiB
Objective-C
Executable File

//
// GDGoogleDriveClient.m
// GDFileManagerExample
//
// Created by Graham Dennis on 24/06/13.
// Copyright (c) 2013 Graham Dennis. All rights reserved.
//
#import "GDGoogleDriveClient.h"
#import "GDGoogleDriveClientManager.h"
#import "GDGoogleDriveChange.h"
#import "AFJSONRequestOperation.h"
#import "GDOAuth2Credential.h"
#import "GDHTTPOperation.h"
@implementation GDGoogleDriveClient
@dynamic credential, apiToken;
- (id)initWithClientManager:(GDClientManager *)clientManager credential:(GDOAuth2Credential *)credential baseURL:(NSURL *)baseURL
{
if (!baseURL)
baseURL = [NSURL URLWithString:@"https://www.googleapis.com/drive/v2/"];
if ((self = [super initWithClientManager:clientManager credential:credential baseURL:baseURL])) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:@"Accept" value:@"text/javascript; text/json; application/json"];
// This is needed to make Google Drive send gzip-encoded responses
NSString *defaultUserAgentHeader = [self defaultValueForHeader:@"User-Agent"];
NSString *gzipUserAgent = [defaultUserAgentHeader stringByAppendingString:@" (gzip)"];
[self setDefaultHeader:@"User-Agent" value:gzipUserAgent];
[self setDefaultHeader:@"Accept-Encoding" value:@"gzip"];
}
return self;
}
- (BOOL)isAvailable
{
return true;
}
#pragma mark - HTTP methods
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters
{
// We need to do this to ensure that PUT/POST URL parameters are encoded in the URL, not in the HTTP body.
NSMutableURLRequest *request = [super requestWithMethod:@"GET" path:path parameters:parameters];
[request setHTTPMethod:method];
return request;
}
- (NSOperation *)enqueueOperationWithMethod:(NSString *)method path:(NSString *)path urlParameters:(NSDictionary *)urlParameters etag:(NSString *)etag
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure
{
return [self enqueueOperationWithMethod:method path:path urlParameters:urlParameters etag:etag
requiresAccessToken:YES
success:success failure:failure];
}
- (BOOL)authorizeRequest:(NSMutableURLRequest *)urlRequest
{
if ([self.credential isAccessTokenValid]) {
NSString *authorizationHeader = [NSString stringWithFormat:@"Bearer %@", self.credential.oauthCredential.accessToken];
[urlRequest setValue:authorizationHeader forHTTPHeaderField:@"Authorization"];
return YES;
}
return NO;
}
- (BOOL)isAuthenticationFailureError:(NSError *)error
{
return [[error domain] isEqualToString:GDHTTPStatusErrorDomain] && [error code] == 401;
}
- (NSOperation *)enqueueOperationWithMethod:(NSString *)method path:(NSString *)path urlParameters:(NSDictionary *)urlParameters etag:(NSString *)etag
requiresAccessToken:(BOOL)requiresAccessToken
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure
{
if (!urlParameters[@"prettyPrint"]) {
NSMutableDictionary *mutableURLParameters = [NSMutableDictionary dictionaryWithDictionary:urlParameters];
mutableURLParameters[@"prettyPrint"] = @"false";
urlParameters = [mutableURLParameters copy];
}
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:urlParameters];
if (etag) {
[request setValue:etag forHTTPHeaderField:@"If-None-Match"];
}
return [self enqueueOperationWithURLRequest:request
requiresAuthentication:requiresAccessToken
success:^(__unused AFHTTPRequestOperation *requestOperation, id responseObject) {
if (success) success(responseObject);
}
failure:^(__unused AFHTTPRequestOperation *requestOperation, NSError *error) {
if (etag && [error code] == 304 && [[error domain] isEqualToString:GDHTTPStatusErrorDomain]) {
// not-modified
if (success) success(nil);
} else {
if (failure) failure(error);
}
}];
}
- (NSOperation *)enqueueOperationWithURLRequest:(NSMutableURLRequest *)urlRequest
requiresAuthentication:(BOOL)requiresAuthentication
shouldRetryBlock:(BOOL (^)(NSError *))shouldRetryBlock
success:(void (^)(AFHTTPRequestOperation *, id))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *))failure
configureOperationBlock:(void (^)(AFHTTPRequestOperation *))configureOperationBlock
{
return [super enqueueOperationWithURLRequest:urlRequest
requiresAuthentication:requiresAuthentication
shouldRetryBlock:shouldRetryBlock
success:success
failure:failure
configureOperationBlock:^(AFHTTPRequestOperation *requestOperation) {
[requestOperation setCacheResponseBlock:^NSCachedURLResponse *(__unused NSURLConnection *connection, __unused NSCachedURLResponse *cachedResponse) {
return nil;
}];
if (configureOperationBlock)
configureOperationBlock(requestOperation);
}];
}
#pragma mark - Access token
- (void)getAccessTokenWithSuccess:(void (^)(GDOAuth2Credential *))success failure:(void (^)(NSError *))failure
{
AFOAuth2Client *oauthClient = [AFOAuth2Client clientWithBaseURL:[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/"]
clientID:self.apiToken.key secret:self.apiToken.secret];
[oauthClient authenticateUsingOAuthWithPath:@"token"
refreshToken:self.credential.oauthCredential.refreshToken
success:^(AFOAuthCredential *credential)
{
GDOAuth2Credential *gdCredential = [[GDOAuth2Credential alloc] initWithOAuthCredential:credential
userID:self.credential.userID
apiToken:self.apiToken];
if (success) success(gdCredential);
} failure:^(NSError *error) {
NSError *httpError = [self httpErrorWithErrorDomain:GDHTTPStatusErrorDomain fromAFNetworkingError:error];
if (failure) failure(httpError ?: error);
}];
}
#pragma mark - Account operations
- (void)getAccountInfoWithSuccess:(void (^)(GDGoogleDriveAccountInfo *))success failure:(void (^)(NSError *))failure
{
[self enqueueOperationWithMethod:@"GET"
path:@"/oauth2/v1/userinfo"
urlParameters:nil
etag:nil
success:^(id response) {
if (![response isKindOfClass:[NSDictionary class]]) {
if (failure) failure(nil);
return;
}
GDGoogleDriveAccountInfo *accountInfo = [[GDGoogleDriveAccountInfo alloc] initWithDictionary:response];
if (success) {
success(accountInfo);
}
} failure:^(NSError *error) {
NSLog(@"error: %@", error);
if (failure) failure(error);
}];
}
#pragma mark - Metadata operations
- (NSString *)groupedMetadataFieldsSelectorForSelector:(NSString *)fieldsSelector
{
if (!fieldsSelector) return @"";
else return [NSString stringWithFormat:@"(%@)", fieldsSelector];
}
- (void)getMetadataForFileID:(NSString *)fileID success:(void (^)(GDGoogleDriveMetadata *metadata))success failure:(void (^)(NSError *error))failure
{
[self getMetadataForFileID:fileID etag:nil success:success failure:failure];
}
- (void)getMetadataForFileID:(NSString *)fileID etag:(NSString *)etag
success:(void (^)(GDGoogleDriveMetadata *metadata))success failure:(void (^)(NSError *error))failure
{
[self getMetadataForFileID:fileID
etag:etag
metadataFields:self.defaultMetadataFields
success:success failure:failure];
}
- (void)getMetadataForFileID:(NSString *)fileID etag:(NSString *)etag metadataFields:(NSString *)metadataFields
success:(void (^)(GDGoogleDriveMetadata *metadata))success failure:(void (^)(NSError *error))failure
{
NSParameterAssert(fileID);
NSString *path = [@"files/" stringByAppendingString:fileID];
NSMutableDictionary *urlParameters = nil;
if (metadataFields) {
urlParameters = [NSMutableDictionary new];
urlParameters[@"fields"] = metadataFields;
}
[self enqueueOperationWithMethod:@"GET"
path:path
urlParameters:urlParameters
etag:etag
success:^(id response) {
GDGoogleDriveMetadata *metadata = nil;
if (response) {
NSParameterAssert([response isKindOfClass:[NSDictionary class]]);
metadata = [[GDGoogleDriveMetadata alloc] initWithDictionary:response];
}
if (success) success(metadata);
} failure:failure];
}
- (void)getContentsOfFileID:(NSString *)fileID success:(void (^)(NSArray *contents, NSString *etag))success failure:(void (^)(NSError *error))failure
{
return [self getContentsOfFileID:fileID etag:nil success:success failure:failure];
}
- (void)getContentsOfFileID:(NSString *)fileID etag:(NSString *)etag success:(void (^)(NSArray *, NSString *))success failure:(void (^)(NSError *))failure
{
return [self getContentsOfFileID:fileID etag:etag metadataFields:self.defaultMetadataFields success:success failure:failure];
}
- (void)getContentsOfFileID:(NSString *)fileID etag:(NSString *)etag metadataFields:(NSString *)metadataFields
success:(void (^)(NSArray *, NSString *))success failure:(void (^)(NSError *))failure
{
NSString *queryString = [NSString stringWithFormat:@"'%@' in parents and trashed = false", fileID];
return [self getFileListWithQuery:queryString etag:etag metadataFields:metadataFields success:success failure:failure];
}
- (void)getFileListWithQuery:(NSString *)query etag:(NSString *)etag metadataFields:(NSString *)metadataFields
success:(void (^)(NSArray *, NSString *))success failure:(void (^)(NSError *))failure
{
NSMutableArray *resultsArray = [NSMutableArray new];
return [self getFileListWithQuery:query pageToken:nil etag:etag metadataFields:metadataFields resultsArray:resultsArray
success:^(NSString *etag) {
if (success) return success([resultsArray copy], etag);
} failure:failure];
}
- (void)getFileListWithQuery:(NSString *)query pageToken:(NSString *)pageToken etag:(NSString *)etag
metadataFields:(NSString *)metadataFields resultsArray:(NSMutableArray *)resultsArray
success:(void (^)(NSString *etag))success failure:(void (^)(NSError *))failure
{
NSMutableDictionary *urlParameters = [NSMutableDictionary new];
if (query)
urlParameters[@"q"] = query;
if (pageToken)
urlParameters[@"pageToken"] = pageToken;
urlParameters[@"maxResults"] = @"1000";
urlParameters[@"fields"] = [NSString stringWithFormat:@"etag,nextPageToken,items%@", [self groupedMetadataFieldsSelectorForSelector:metadataFields]];
[self enqueueOperationWithMethod:@"GET"
path:@"files"
urlParameters:urlParameters
etag:etag
success:^(id response) {
if (response) {
NSParameterAssert([response isKindOfClass:[NSDictionary class]]);
NSDictionary *dictionary = (NSDictionary *)response;
[self appendMetadataFromFileListResponse:dictionary toArray:resultsArray];
NSString *rootETag = dictionary[@"etag"];
NSString *nextPageToken = dictionary[@"nextPageToken"];
if (nextPageToken) {
[self getFileListWithQuery:query
pageToken:nextPageToken
etag:nil
metadataFields:metadataFields
resultsArray:resultsArray
success:^(__unused NSString *etag) {
if (success) success(rootETag);
} failure:failure];
return;
} else {
if (success) success(rootETag);
}
}
} failure:failure];
}
- (void)appendMetadataFromFileListResponse:(NSDictionary *)responseDictionary toArray:(NSMutableArray *)array
{
for (NSDictionary *itemMetadata in responseDictionary[@"items"]) {
GDGoogleDriveMetadata *metadata = [[GDGoogleDriveMetadata alloc] initWithDictionary:itemMetadata];
[array addObject:metadata];
}
}
- (void)getRevisionHistoryForFileID:(NSString *)fileID
success:(void (^)(NSArray *history))success
failure:(void (^)(NSError *error))failure
{
NSParameterAssert(fileID);
NSString *path = [NSString pathWithComponents:@[@"files", fileID, @"revisions"]];
[self enqueueOperationWithMethod:@"GET"
path:path
urlParameters:nil
etag:nil
success:^(id response) {
if ([response isKindOfClass:[NSDictionary class]]) {
NSDictionary *responseDict = response;
NSArray *rawMetadataArray = responseDict[@"items"];
NSMutableArray *versionHistory = [NSMutableArray arrayWithCapacity:[rawMetadataArray count]];
for (NSDictionary *rawMetadata in rawMetadataArray) {
GDGoogleDriveMetadata *metadata = [[GDGoogleDriveMetadata alloc] initWithDictionary:rawMetadata];
if (metadata)
[versionHistory addObject:metadata];
}
NSArray *newestFirstVersionHistory = [[versionHistory reverseObjectEnumerator] allObjects];
if (success) success(newestFirstVersionHistory);
} else {
if (failure) failure(nil);
}
} failure:failure];
}
- (void)trashFileID:(NSString *)fileID success:(void (^)(GDGoogleDriveMetadata *metadata))success failure:(void (^)(NSError *error))failure
{
NSParameterAssert(fileID);
NSString *path = [NSString pathWithComponents:@[@"files", fileID, @"trash"]];
NSMutableDictionary *urlParameters = [NSMutableDictionary new];
if (self.defaultMetadataFields)
urlParameters[@"fields"] = self.defaultMetadataFields;
[self enqueueOperationWithMethod:@"POST"
path:path
urlParameters:urlParameters
etag:nil
success:^(id response) {
GDGoogleDriveMetadata *metadata = nil;
if (response) {
NSParameterAssert([response isKindOfClass:[NSDictionary class]]);
metadata = [[GDGoogleDriveMetadata alloc] initWithDictionary:response];
}
if (success) success(metadata);
} failure:failure];
}
- (void)deleteFileID:(NSString *)fileID success:(void (^)())success failure:(void (^)(NSError *error))failure
{
NSParameterAssert(fileID);
NSString *path = [NSString pathWithComponents:@[@"files", fileID]];
[self enqueueOperationWithMethod:@"DELETE"
path:path
urlParameters:nil
etag:nil
success:^(__unused id response) {
if (success) success();
} failure:failure];
}
static NSString *const GDGoogleDriveCopyOperation = @"COPY";
static NSString *const GDGoogleDriveMoveOperation = @"MOVE";
- (void)copyFileID:(NSString *)fileID toParentIDs:(NSArray *)parentIDs name:(NSString *)name
success:(void (^)(GDGoogleDriveMetadata *metadata))success failure:(void (^)(NSError *error))failure
{
return [self copyOrMove:GDGoogleDriveCopyOperation fromFileID:fileID toParentIDs:parentIDs name:name success:success failure:failure];
}
- (void)moveFileID:(NSString *)fileID toParentIDs:(NSArray *)parentIDs name:(NSString *)name success:(void (^)(GDGoogleDriveMetadata *))success failure:(void (^)(NSError *))failure
{
return [self copyOrMove:GDGoogleDriveMoveOperation fromFileID:fileID toParentIDs:parentIDs name:name success:success failure:failure];
}
- (void)copyOrMove:(NSString *)operation fromFileID:(NSString *)fileID toParentIDs:(NSArray *)parentIDs name:(NSString *)name success:(void (^)(GDGoogleDriveMetadata *))success failure:(void (^)(NSError *))failure
{
NSParameterAssert(fileID);
NSParameterAssert(parentIDs);
NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:@[@"files", fileID]];
if ([operation isEqualToString:GDGoogleDriveCopyOperation])
[pathComponents addObject:@"copy"];
NSString *path = [NSString pathWithComponents:pathComponents];
NSMutableDictionary *jsonDict = [NSMutableDictionary new];
if (parentIDs)
jsonDict[@"parents"] = parentIDs;
if (name)
jsonDict[@"title"] = name;
NSString *method = [operation isEqualToString:GDGoogleDriveCopyOperation] ? @"POST" : @"PATCH";
NSMutableURLRequest *urlRequest = [self requestWithMethod:method path:path parameters:nil];
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDict options:0 error:&error];
if (!jsonData) {
if (failure) failure(error);
return;
}
[urlRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[urlRequest setHTTPBody:jsonData];
[self enqueueOperationWithURLRequest:urlRequest requiresAuthentication:YES success:^(__unused AFHTTPRequestOperation *requestOperation, id response) {
GDGoogleDriveMetadata *metadata = nil;
if (response) {
NSParameterAssert([response isKindOfClass:[NSDictionary class]]);
metadata = [[GDGoogleDriveMetadata alloc] initWithDictionary:response];
}
if (success) success(metadata);
} failure:^(__unused AFHTTPRequestOperation *requestOperation, NSError *error) {
if (failure) failure(error);
}];
}
#pragma mark - Download
- (NSOperation *)downloadFileID:(NSString *)fileID intoPath:(NSString *)localPath
progress:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))progress
success:(void (^)(NSString *localPath, GDGoogleDriveMetadata *metadata))success_
failure:(void (^)(NSError *error))failure_
{
__block GDParentOperation *parentOperation = [GDParentOperation new];
dispatch_block_t cleanup = ^{[parentOperation finish]; parentOperation = nil;};
typeof(success_) success = ^(NSString *localPath, GDGoogleDriveMetadata *metadata){
dispatch_async(parentOperation.successCallbackQueue, ^{
if (success_) success_(localPath, metadata);
cleanup();
});
};
typeof(failure_) failure = ^(NSError *error){
dispatch_async(parentOperation.failureCallbackQueue, ^{
if (failure_) failure_(error);
cleanup();
});
};
NSString *metadataFields = self.defaultMetadataFields;
if (metadataFields) {
metadataFields = [metadataFields stringByAppendingString:@",downloadUrl"];
} else {
metadataFields = @"downloadUrl";
}
[self getMetadataForFileID:fileID etag:nil metadataFields:metadataFields
success:^(GDGoogleDriveMetadata *metadata) {
NSString *downloadURLString = [metadata downloadURLString];
NSMutableURLRequest *urlRequest = [self requestWithMethod:@"GET" path:downloadURLString parameters:nil];
[urlRequest setValue:@"*/*" forHTTPHeaderField:@"Accept"];
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:localPath append:NO];
[self enqueueOperationWithURLRequest:urlRequest
requiresAuthentication:YES
shouldRetryBlock:NULL
success:^(__unused AFHTTPRequestOperation *requestOperation, __unused id responseObject) {
success(localPath, metadata);
}
failure:^(__unused AFHTTPRequestOperation *requestOperation, NSError *error) {
failure(error);
}
configureOperationBlock:^(AFHTTPRequestOperation *requestOperation) {
[parentOperation addChildOperation:requestOperation];
requestOperation.outputStream = outputStream;
[requestOperation setDownloadProgressBlock:progress];
}];
} failure:failure];
[parentOperation start];
return parentOperation;
}
#pragma mark - Change operations
- (void)getAllChangesWithStartChangeID:(NSString *)changeID success:(void (^)(NSArray *, NSNumber *))success failure:(void (^)(NSError *))failure
{
[self getAllChangesWithStartChangeID:changeID metadataFields:self.defaultMetadataFields success:success failure:failure];
}
- (void)getAllChangesWithStartChangeID:(NSNumber *)changeID metadataFields:(NSString *)metadataFields
success:(void (^)(NSArray *changes, NSNumber *largestChangeID))success failure:(void (^)(NSError *error))failure
{
NSDictionary *urlParameters = @{@"startChangeId": [changeID stringValue]};
[self getChangesWithURLParameters:urlParameters metadataFields:metadataFields success:success failure:failure];
}
- (void)getChangesFromLastKnownChangeID:(NSNumber *)lastKnownChangeID success:(void (^)(NSArray *, NSNumber *))success failure:(void (^)(NSError *))failure
{
[self getChangesFromLastKnownChangeID:lastKnownChangeID metadataFields:self.defaultMetadataFields success:success failure:failure];
}
- (void)getChangesFromLastKnownChangeID:(NSNumber *)lastKnownChangeID metadataFields:(NSString *)metadataFields
success:(void (^)(NSArray *changes, NSNumber *largestChangeID))success failure:(void (^)(NSError *error))failure
{
long long nextChangeId = [lastKnownChangeID longLongValue] + 1LL;
NSDictionary *urlParameters = @{@"pageToken": [[NSNumber numberWithLongLong:nextChangeId] stringValue]};
[self getChangesWithURLParameters:urlParameters metadataFields:metadataFields success:success failure:failure];
}
- (void)getChangesWithURLParameters:(NSDictionary *)urlParameters metadataFields:(NSString *)metadataFields
success:(void (^)(NSArray *changes, NSNumber *largestChangeID))success failure:(void (^)(NSError *error))failure
{
NSMutableDictionary *mutableURLParameters = [NSMutableDictionary dictionaryWithDictionary:urlParameters];
mutableURLParameters[@"fields"] = [NSString stringWithFormat:@"etag,nextPageToken,items%@", [self groupedMetadataFieldsSelectorForSelector:metadataFields]];
NSMutableArray *results = [NSMutableArray new];
return [self getChangesWithURLParameters:mutableURLParameters
resultsArray:results
success:^(NSNumber *largestChangeID) {
if (success) {
success([results copy], largestChangeID);
}
} failure:failure];
}
- (void)getChangesWithURLParameters:(NSDictionary *)urlParameters resultsArray:(NSMutableArray *)resultsArray
success:(void (^)(NSNumber *largestChangeID))success failure:(void (^)(NSError *error))failure
{
if (!urlParameters) {
urlParameters = [NSDictionary new];
}
if (!urlParameters[@"maxResults"]) {
NSMutableDictionary *modifiedURLParameters = [urlParameters mutableCopy];
modifiedURLParameters[@"maxResults"] = @"1000";
urlParameters = [modifiedURLParameters copy];
}
[self enqueueOperationWithMethod:@"GET"
path:@"changes"
urlParameters:urlParameters
etag:nil
success:^(id response) {
NSParameterAssert([response isKindOfClass:[NSDictionary class]]);
NSDictionary *dictionary = (NSDictionary *)response;
[self appendChangesFromChangeListResponse:dictionary toArray:resultsArray];
NSString *nextPageToken = dictionary[@"nextPageToken"];
if (nextPageToken) {
NSMutableDictionary *nextPageURLParameters = [urlParameters mutableCopy];
nextPageURLParameters[@"pageToken"] = nextPageToken;
[self getChangesWithURLParameters:nextPageURLParameters
resultsArray:resultsArray
success:success
failure:failure];
return;
} else {
NSString *largestChangeIDString = dictionary[@"largestChangeId"];
NSNumber *largestChangeID = [NSNumber numberWithLongLong:[largestChangeIDString longLongValue]];
if (success) success(largestChangeID);
}
} failure:failure];
}
- (void)appendChangesFromChangeListResponse:(NSDictionary *)responseDictionary toArray:(NSMutableArray *)array
{
for (NSDictionary *changeDictionary in responseDictionary[@"items"]) {
GDGoogleDriveChange *change = [[GDGoogleDriveChange alloc] initWithDictionary:changeDictionary];
[array addObject:change];
}
}
@end