Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I upload files to Google Cloud Storage in Objective-C/iOS?

I've been looking for documentation on how to upload files to my 'bucket' in Google Cloud Storage from my iOS app, but I can't find anything at all on the subject. No documentations, no tutorials, no example projects. Am I completely blind? I am simply trying to find a way for my app-users to upload a file to a "public bucket", and get a URL in return. All I can find is chunks of HTTP-protocols or JSON etc, and I have no idea how to use that, but there's no reference to it either. It feels like the author of those documentations expects me to know everything already. I've found some OSX-example codes, but they are too without documentation, and I've been trying to read the code they have provided, but with no luck.

What I'm looking for is something like this: (This code is made up. It's what I want. I noticed Google used the prefix GTL* for their classes)

NSData *dataToUpload = ... ; //Or UIImage or some movie-format or whatever
NSURL *destination;

GTLStorageUploader *uploader = [GTLStorageUploader alloc]initWithBucket:@"myBucket" withHashOrKeyOrSomething:@"a1b2c3hashkeyOrWhatever"];
destination = [uploader uploadData:dataToUpload];//inBackground etc..

It's actually easier than this when using Parse.com, but there's simply not enough storage space for my app there, so I need to be able to upload the data files to Google Cloud Storage. How?

like image 554
Sti Avatar asked Nov 12 '22 23:11

Sti


1 Answers

I did eventually get this to work. It wasn't pretty, though. It's quite a long time ago now, so I can't really remember all the logic etc, but I'll post what made it work, and change the ID-stuff. I hope it helps, and sorry I didn't remember to write this when I found out and it was fresh in mind. I don't have time to get into this at the moment.

An important note: I also had to change a lot of permissions on the bucket and on the users/authorized on GoogleCloudStorage to make this work. We tried so many different combinations, but I THINK this was the stuff we did:

  • On each bucket: "Allow everyone to upload/delete/edit etc".
  • On the auth for the entire CloudStorage: "Allow only entities with certain accessToken to access this CloudStorage.
  • Allow only www.yourAppEngineURL.com to request such an accessToken.

This felt wrong, and still does. If anyone gets a hold of this accessToken, they can do whatever they want as long as that accessToken is valid. Like.. delete all files. But that was the only way we could make it work. Of course, only authorized users could request this accessToken from OUR appEngine, but still.. meh. I'm no security-guru, and this was just a fun project, so we let it go. Now to the code.

When uploading:

GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];

serv.additionalHTTPHeaders = [NSDictionary dictionaryWithObjectsAndKeys:
                               @"123123123", @"x-goog-project-id",
                              @"application/json-rpc", @"Content-Type",
                               @"application/json-rpc", @"Accept", nil];


GTLStorageObjectAccessControl *objAccessControl = [GTLStorageObjectAccessControl new];
objAccessControl.entity = @"project-owners-123456"; //Some value from the control panel on CloudStorage or something. Or Apps.. Or AppEngine, not sure.
//Probably connected to the accessToken my AppEngine requests and sends to users.
objAccessControl.role = @"OWNER";

GTLStorageObjectAccessControl *objAccessControl2 = [GTLStorageObjectAccessControl new];
objAccessControl2.entity = @"allUsers";
objAccessControl2.role = @"READER";
//Don't remember why I need both. Or what they do. Hey ho.
//It looks like.. Everybody can read. Only my authorized accessToken-people can write? probably.

GTLStorageBucket *bucket = [[GTLStorageBucket alloc] init];
bucket.name = @"my_bucket";

NSError *err;
NSFileHandle *fileHandle = [NSFileHandle myLocalFileURLiThink error:&err];
if(err)
{
    NSLog(@"Some error here");
}

GTLUploadParameters *params = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:@"video/mp4 (or something else)"];
GTLStorageObject *storageObject = [[GTLStorageObject alloc] init];
storageObject.acl = @[objAccessControl, objAccessControl2];


NSString *key = [NSString stringWithFormat:@"fileName.mp4"];
// should probably be unique.. The url will be cloud.com/my_bucket/fileName.mp4
//You can generate something unique by putting together the user's userID and the timeStamp for the dateTime right now. 
//This user will never upload two things within this second.
storageObject.name = key;

After this point in the code I do some magic I'm not gonna post. I ask for and receive an accessToken to use on GoogleCloudStorage from our own API. I don't remember where or how I got that token to begin with, but I believe that the backend (AppEngine) had to request it from the CloudStorage-thing, using a pretty standard call. And, as I said in the beginning, we changed some settings on CloudStorage making our AppEngine the only entity allowed to request this token. Or something.. This token has a lifecycle of like.. 15 minutes or so.. I don't know, it's provided by some Google-default-thing. I might look into it later if any of you need it.

NSString *receivedAccessToken = @"abc123"; //Received from CloudStorage via AppEngine.
NSString *accessToken = @"Bearer %@", receivedAccessToken" //(pseudo) Because it needed to say "Bearer " first. Don't know why, or how I found out..

[serv setAdditionalHTTPHeaders:[NSDictionary dictionaryWithObject:accessToken forKey:@"Authorization"]];

//Upload-magic:
GTLQueryStorage *query = [GTLQueryStorage queryForObjectsInsertWithObject:storageObject bucket:bucket.name uploadParameters:params];

GTLServiceTicket *t = [serv executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
    //Handle error first.
    NSLog(@"Success!");
    dispatch_async(dispatch_get_main_queue(), ^{
        actualURL = [NSString stringWithFormat:@"%@%@", yourFullBucketURL /*( e.g www.googleCloud.com/my_bucket)*/, key /*file.mp4*/];
    });
}];

And you can track the progress of the upload after this block, with the ticket-object (like, t.uploadProgressBlock = ...).

This code has been edited quite a bit for Stack-purposes now, so I might have screwed up something, so I probably doesn't work exactly like this. But read it, and try to understand how it works. Good luck. If you have the option, stay with Amazon or something else, this was not fun to work with. Worst documentation ever. Also, Amazon's S3 uploads/downloads faster than GoogleCloudStorage. I regret changing from Amazon to Google. Amazon had so much better API too, almost like the one in my question.

Here's the code used by AppEngine to request the AccessToken:

private GoogleCredential getGoogleCredential(String scope) throws GeneralSecurityException, IOException
{
    JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    GoogleCredential credential = new GoogleCredential.Builder()
                    .setTransport(httpTransport)
                    .setJsonFactory(JSON_FACTORY)
                    .setServiceAccountId(Constants.SERVICE_ACCOUNT_EMAIL)
                    .setServiceAccountPrivateKeyFromP12File(new File(Constants.CLOUD_STORAGE_KEY))
                    .setServiceAccountScopes(Collections.singleton(scope))
                    .build();
    return credential;
}

The parameter "scope" sent in to this method is either https://www.googleapis.com/auth/devstorage.read_only or https://www.googleapis.com/auth/devstorage.read_write

The method returns a GoogleCredential-object, on which you can call googleCredential.refreshToken(). I believe this is the call made to get the token. I'm not sure though.

The Constants (email and key) are stuff you need to set up and get from the auth-page on Google Cloud Storage, I think.

This documentation should cover some of it (it looks more documented now than it did then, I think): https://cloud.google.com/storage/docs/authentication

like image 162
Sti Avatar answered Nov 15 '22 05:11

Sti