AWS Objective-C Image Upload - objective-c

I have an older application whos image upload has begun failing, the code used it below:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSError * __block anError = nil;
AWSS3PutObjectRequest *por = [AWSS3PutObjectRequest new];
por.key = key;
por.bucket = bucket;
por.contentType = #"image/png";
por.contentLength = [NSNumber numberWithUnsignedLong:[UIImagePNGRepresentation(image) length]];
por.body = UIImagePNGRepresentation(image);
por.ACL = AWSS3ObjectCannedACLPublicRead;
[[AWSS3 defaultS3] putObject:por completionHandler:^(AWSS3PutObjectOutput * _Nullable response, NSError * _Nullable error) {
anError = error;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (anError) {
NSException *exception = [NSException exceptionWithName:#"NoahPutObjectException" reason:[anError description] userInfo:nil];
#throw exception;
}
I can confirm the bucket has not changed and is in the correct region. The error I receive is this:
Error Domain=com.amazonaws.AWSS3ErrorDomain Code=0 "(null)" UserIn
fo={RequestId=F5B5BFDB18414371,
Bucket=BUCKETREDACTED, HostId=HOSTIDREDACTED,
Message=The bucket you are attempting to access must be addressed
using the specified endpoint. Please send all future requests to this endpoint., Code=PermanentRedirect,
Endpoint=s3.amazonaws.com}
I have updated to using the most recent pod, but still receive this error.

Looks to me like you are using "s3.amazonaws.com" as your endpoint, and you need to instead use a region-specific endpoint address like "s3-us-west-1.amazonaws.com". The correct endpoint URLs, broken down by region, can be found here: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region

Related

How to get data out of a block?

I'm trying to make an equivalent to the .NET recognize() call, which is synchronous, for ios in objective-c. I found code to recognize speech but the string that was recognized is only inside a block.
I've tried making the block not a block (it seems to be part of the API that it be a block), making __block variables and returning their values, also out parameters in the caller/declarer of the block; finally I wrote a file while in the block and read the file outside. It still didn't work like I want because of being asynchronous although I at least got some data out. I also tried writing to a global variable from inside the block and reading it outside.
I'm using code from here: How to implement speech-to-text via Speech framework, which is (before I mangled it):
/*!
* #brief Starts listening and recognizing user input through the
* phone's microphone
*/
- (void)startListening {
// Initialize the AVAudioEngine
audioEngine = [[AVAudioEngine alloc] init];
// Make sure there's not a recognition task already running
if (recognitionTask) {
[recognitionTask cancel];
recognitionTask = nil;
}
// Starts an AVAudio Session
NSError *error;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord error:&error];
[audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
// Starts a recognition process, in the block it logs the input or stops the audio
// process if there's an error.
recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
AVAudioInputNode *inputNode = audioEngine.inputNode;
recognitionRequest.shouldReportPartialResults = YES;
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
BOOL isFinal = NO;
if (result) {
// Whatever you say in the microphone after pressing the button should be being logged
// in the console.
NSLog(#"RESULT:%#",result.bestTranscription.formattedString);
isFinal = !result.isFinal;
}
if (error) {
[audioEngine stop];
[inputNode removeTapOnBus:0];
recognitionRequest = nil;
recognitionTask = nil;
}
}];
// Sets the recording format
AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
[inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[recognitionRequest appendAudioPCMBuffer:buffer];
}];
// Starts the audio engine, i.e. it starts listening.
[audioEngine prepare];
[audioEngine startAndReturnError:&error];
NSLog(#"Say Something, I'm listening");
}
I want to call Listen(), (like startListening() above), have it block execution until done, and have it return the string that was said. But actually I would be thrilled just to get result.bestTranscription.formattedString somehow to the caller of startListening().
I'd recommend you to take another approach. In Objective-C having a function that blocks for a long period of time is an anti-pattern.
In this language there's no async/await, nor cooperative multitasking, so blocking for long-ish periods of time might lead to resource leaks and deadlocks. Moreover if done on the main thread (where the app UI runs), the app might be forcefully killed by the system due to being non-responsive.
You should use some asynchronous patterns such as delegates or callbacks.
You might also try using some promises library to linearize your code a bit, and make it look "sequential".
The easiest approach with callbacks would be to pass a completion block to your "recognize" function and call it with the result string when it finishes:
- (void)recognizeWithCompletion:(void (^)(NSString *resultString, NSError *error))completion {
...
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest
resultHandler:^(SFSpeechRecognitionResult *result, NSError *error)
{
...
dispatch_async(dispatch_get_main_queue(), ^{
completion(result.bestTranscription.formattedString, error);
});
...
}];
...
}
Note that the 2nd parameter (NSError) - is an error in case the caller wants to react on that too.
Caller side of this:
// client side - add this to your UI code somewhere
__weak typeof(self) weakSelf = self;
[self recognizeWithCompletion:^(NSString *resultString, NSError *error) {
if (!error) {
[weakSelf processCommand:resultString];
}
}];
// separate method
- (void)processCommand:(NSString *command) {
// can do your processing here based on the text
...
}

Error Handling Objective C during no response

In my app I use following methods to POST/GET data from a remote server.
postData = [self sendSynchronousRequest:request returningResponse:&response error:&error];
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0];
}
*response = resp;
*error = err;
return data;
}
I have basic error handling for no network connectivity, and app directs users to no network connectivity viewController. But I would like to account for situations when for instance server is down or data format of api has changed, Just wondering how would I catch such errors and prevent the app from crashing.
A couple of general points:
Don't do requests synchronously. Pass in a block with code that should run when the request completes. There's no reason to tie up a thread waiting for a completion handler to run, and it is even worse if that thread is basically continuously running as yours is.
If you absolutely must do that (why?), at least use a semaphore and wait on the semaphore. That way, you aren't burning a CPU core continuously while you wait.
The way you avoid crashing is to sanity check the data you get back, e.g.
Check the length of the data and make sure you didn't get nil/empty response data.
Assuming the server sends a JSON response, wrap your NSJSONSerialization parsing in an #try block, and if it fails or throws an exception, handle the error in some useful way.
Generally assume that all data is invalid until proven otherwise. Check to make sure the data makes sense.
If you're in charge of the server side, consider passing an API version as an argument, and as you modify things in the future, make sure that incompatible changes occur only when responding to clients that request a newer API version (or, alternatively, send a response that tells the client that it needs to be upgraded, and then refuse to provide data).

Error: Upload file to google drive using objective-c

Now I'm trying to upload file to Google Drive.
Code was built successfully, but my file didn't upload to Google Drive.
Here is my code.
-(void)uploadFileToGoogleDrive : (NSString*)fileName fileFullPath:(NSString*)filePath {
GTLDriveFile *driveFile = [GTLDriveFile object];
driveFile.mimeType = #"application/pdf";
driveFile.originalFilename = filePath;
driveFile.name = fileName;
NSString *parentId = #"root";
driveFile.parents = #[parentId];
NSData *fileContent = [[NSData alloc] initWithContentsOfFile:fileName];
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithData:fileContent MIMEType:#"application/pdf"];
GTLQueryDrive *query = [GTLQueryDrive queryForFilesCreateWithObject:driveFile uploadParameters:uploadParameters];
[self.service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLDriveFile *updatedFile,
NSError *error) {
if (error == nil) {
NSLog(#"\nfile uploaded into google drive");
} else {
NSLog(#"\nAn error occurred: %#", error);
}
}];
}
Error message is like this:
Error Domain=com.google.HTTPStatus Code=501 "(null)"
Please help me.
Based from this blog, error 501 occurs more likely if the client is not a Web browser - particularly if the Web server is old. In either case if the client has specified a valid request type, then the Web server is either responding incorrectly or simply needs to be upgraded.
As stated in this answer, make sure that you have permission to make changes in these two actions:
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:self.fromPath];
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:mimeType];
GTLQueryDrive *uploadFileQuery = [GTLQueryDrive queryForFilesCreateWithObject:toFile
uploadParameters:uploadParameters];
You can also check on this related threads:
not be able to upload a file to Google Drive
Cannot upload/download files through google drive api

How to use automatic retry in Google Drive / Fetcher services in Objective C

The problem
I am using google-api-objectivec-client library to work with Google Drive API.
I am creating 10 folders inside appDataFolder (for example) without any delays and it is too fast and I am constantly getting errors for 9/10 of my requests:
Error Domain=com.google.GTLJSONRPCErrorDomain Code=500 "(Internal Error)" UserInfo={error=Internal Error, NSLocalizedFailureReason=(Internal Error)
some times these:
Error Domain=com.google.GTLJSONRPCErrorDomain Code=403 "(User Rate Limit Exceeded)" UserInfo={error=User Rate Limit Exceeded, NSLocalizedFailureReason=(User Rate Limit Exceeded),
Most likely, it is related to "spamming" or userRateLimit exceptions, because it easily can be fixed by "sleeps" (have reproduced this many times).
Suggested solution
Google suggest to retry request with such kind of errors, even it provides great API to do it automatically.
According to documentation:
GTL service classes and the GTMSessionFetcher class provide a mechanism for automatic retry of a few common network and server errors, with appropriate increasing delays between each attempt. You can turn on the automatic retry support for a GTL service by setting the retryEnabled property.
// Turn on automatic retry of some common error results
service.retryEnabled = YES;
The default errors retried are http status 408 (request timeout), 503 (service unavailable), and 504 (gateway timeout), NSURLErrorNetworkConnectionLost, and NSURLErrorTimedOut. You may specify a maximum retry interval other than the default of 1 minute, and can provide an optional retry selector to customize the criteria for each retry attempt.
But error with status 500 is not in default list. So, I tried to use this:
// Retry selector is optional for retries.
// If present, it should have the signature:
// -(BOOL)ticket:(GTLServiceTicket *)ticket willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
// and return YES to cause a retry. Note that unlike the fetcher retry
// selector, this selector's first argument is a ticket, not a fetcher.
GTLService.retrySelector
I have created a method which constantly returns YES, but it never get called and as a result request with errors are not retried.
Question
How to use automatic retry for google-api-objectivec-client?
Update. Code sample to reproduce the issue:
- (void) reproduceTheIssue: (GTMOAuth2Authentication *) authorizer {
GTLServiceDrive * driveService = [[GTLServiceDrive alloc] init];
driveService.retryEnabled = true;
driveService.authorizer = authorizer;
// driveService.fetcherService.retryEnabled = YES; //this also doesn't help
// driveService.retrySelector = #selector(ticket:willRetry:forError:);
const int DELAY = 0;
const int NUMBER_OF_FOLDERS = 50;
for (int i = 0; i < NUMBER_OF_FOLDERS; i++) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DELAY * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
GTLDriveFile * nestedFolder = [GTLDriveFile object];
NSString * folderName = [NSString stringWithFormat: #"folder-number-%i", i];
nestedFolder.name = folderName;
nestedFolder.mimeType = #"application/vnd.google-apps.folder";
nestedFolder.parents = #[#"appDataFolder"];
GTLQueryDrive * query = [GTLQueryDrive queryForFilesCreateWithObject: nestedFolder uploadParameters: nil];
[driveService executeQuery: query completionHandler: ^(GTLServiceTicket * ticket, GTLDriveFile * updatedFile, NSError * error) {
if (error == nil) {
NSLog(#"Successfully created a folder %#", updatedFile);
} else {
NSLog(#"Failed to a folder: %# error: %#", folderName, error);
}
}];
});
}
}
- (BOOL) ticket: (GTLServiceTicket *) ticket willRetry: (BOOL) suggestedWillRetry forError: (NSError *) error {
NSLog(#"I will never be called :( with this arguments ticket: %#, willRetry: %i, error :%#", ticket, suggestedWillRetry, error);
return YES;
}
Update Update The issue was discussed at library's GitHub. Short answer: it is not possible to retry these error automatically. It should be managed programmatically by user in handlers.
The retry logic is in the http fetcher class, so the retries only reflect errors that show up during network requests.

Google Drive API - list specific files in a specific folder using Objective-C

I'm working on a Google Drive integration for iOS and I've hit a snag—I need to check for the existence of a file in a folder, but I keep getting a "500 Internal Error" response no matter how I seem to put together the request. I think this should be legit:
// self.directoryDriveFile.identifier is a valid folder identifier retrieved on previous request
GTLQueryDrive *query = [GTLQueryDrive queryForChildrenListWithFolderId:self.directoryDriveFile.identifier];
// add query to specify the file we're interested in (works in Java version...)
query.q = [NSString stringWithFormat:#"title='%#' and trashed=false",
ZTCloudSyncLockFileName];
// stopping in debugger shows query.q: title='sync.lock' and trashed=false
// fire off request
[self.driveService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLDriveFileList *files,
NSError *error) {
if (error == nil) {
if ([files.items count] > 0) {
self.foundLockfile = YES;
} else {
self.foundLockfile = NO;
}
} else {
DLog(#"An error occurred loading lockfile request: %#", error);
[self bailWithError:error performCleanup:NO];
}
}];
When the query is executed, it always ends up in error, with the following unfortunately sparse error information:
Error Domain=com.google.GTLJSONRPC
ErrorDomain Code=500 "The operation couldn’t be completed. (Internal Error)"
UserInfo=0x99c4660 {
error=Internal Error,
GTLStructuredError=GTLErrorObject 0x99c4360: {
message:"Internal Error" code:500 data:[1]},
NSLocalizedFailureReason=(Internal Error)}
I've also tried the following more basic query, specifying a parents clause, but I end up with the same unfortunately spare error object shown above:
GTLQueryDrive *altQuery = [GTLQueryDrive queryForFilesList];
altQuery.q = [NSString stringWithFormat:#"title='%#' and trashed=false and '%#' in parents",
ZTCloudSyncLockFileName,
self.directoryDriveFile.identifier];
That, too, should work, but also produces a 500 error.
Additional information: tested the following while working on this:
Check for folder in directory root—OK
Create folder in directory root—OK
Check for file named sync.lock in directory root—OK
-(void)fetchGoogleDriveFileListWithfolderId:(NSString *)folderId
:(void (^)(NSMutableArray *, NSError*))completionBlock
{
GTLQueryDrive *query = [GTLQueryDrive queryForFilesList];
query.q =[NSString stringWithFormat:#"'%#' in parents and trashed=false", folderId];
GTLServiceTicket *ticketListing = [self.driveService
executeQuery:query completionHandler:^(GTLServiceTicket *ticket,GTLDriveFileList *files, NSError *error)
{
NSMutableArray *mainArray=[[NSMutableArray alloc]init];
if (error == nil)
{
completionBlock(files.items,nill);
}
else
{
NSLog(#"An error occurred: %#", error);
completionBlock(nil,error);
}
}];
}
You can use above method for fetch the folder contents.
Here folderId is
“root” (Main Drive)
“sharedWithMe” for shared folder
GTLDrivefile identifier value