diff --git a/.gitignore b/.gitignore index 50f68a6..086fa36 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build *~.xib .DS_Store xcuserdata/ +*.diff diff --git a/CordovaLib/Classes/CDV.h b/CordovaLib/Classes/CDV.h index 5a0ae6a..15d9316 100755 --- a/CordovaLib/Classes/CDV.h +++ b/CordovaLib/Classes/CDV.h @@ -33,7 +33,6 @@ #import "CDVContact.h" #import "CDVContacts.h" #import "CDVDebug.h" -#import "CDVDebugConsole.h" #import "CDVDevice.h" #import "CDVFile.h" #import "CDVFileTransfer.h" @@ -47,6 +46,7 @@ #import "CDVLocalStorage.h" #import "CDVInAppBrowser.h" #import "CDVScreenOrientationDelegate.h" +#import "CDVTimer.h" #import "NSArray+Comparisons.h" #import "NSData+Base64.h" diff --git a/CordovaLib/Classes/CDVAvailability.h b/CordovaLib/Classes/CDVAvailability.h index 33c6799..b288522 100755 --- a/CordovaLib/Classes/CDVAvailability.h +++ b/CordovaLib/Classes/CDVAvailability.h @@ -17,6 +17,8 @@ under the License. */ +#define __CORDOVA_IOS__ + #define __CORDOVA_0_9_6 906 #define __CORDOVA_1_0_0 10000 #define __CORDOVA_1_1_0 10100 @@ -37,6 +39,8 @@ #define __CORDOVA_2_3_0 20300 #define __CORDOVA_2_4_0 20400 #define __CORDOVA_2_5_0 20500 +#define __CORDOVA_2_6_0 20600 +#define __CORDOVA_2_7_0 20700 #define __CORDOVA_NA 99999 /* not available */ /* @@ -47,7 +51,7 @@ #endif */ #ifndef CORDOVA_VERSION_MIN_REQUIRED - #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_5_0 + #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_7_0 #endif /* @@ -65,12 +69,20 @@ /* Return the string version of the decimal version */ #define CDV_VERSION [NSString stringWithFormat:@"%d.%d.%d", \ - (CORDOVA_VERSION_MIN_REQUIRED / 10000), \ - (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \ - (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100] + (CORDOVA_VERSION_MIN_REQUIRED / 10000), \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100] #ifdef __clang__ #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg))) #else #define CDV_DEPRECATED(version, msg) __attribute__((deprecated())) #endif + +// Enable this to log all exec() calls. +#define CDV_ENABLE_EXEC_LOGGING 0 +#if CDV_ENABLE_EXEC_LOGGING + #define CDV_EXEC_LOG NSLog +#else + #define CDV_EXEC_LOG(...) do {} while (NO) +#endif diff --git a/CordovaLib/Classes/CDVCamera.h b/CordovaLib/Classes/CDVCamera.h index 204d25f..65eac77 100755 --- a/CordovaLib/Classes/CDVCamera.h +++ b/CordovaLib/Classes/CDVCamera.h @@ -61,8 +61,8 @@ typedef NSUInteger CDVMediaType; // ======================================================================= // @interface CDVCamera : CDVPlugin + UINavigationControllerDelegate, + UIPopoverControllerDelegate> {} @property (strong) CDVCameraPicker* pickerController; diff --git a/CordovaLib/Classes/CDVCamera.m b/CordovaLib/Classes/CDVCamera.m index aabe844..823fde9 100755 --- a/CordovaLib/Classes/CDVCamera.m +++ b/CordovaLib/Classes/CDVCamera.m @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one */ #import "CDVCamera.h" +#import "CDVJpegHeaderWriter.h" #import "NSArray+Comparisons.h" #import "NSData+Base64.h" #import "NSDictionary+Extensions.h" @@ -60,6 +61,8 @@ - (BOOL)popoverSupported * 7 allowsEdit * 8 correctOrientation * 9 saveToPhotoAlbum + * 10 popoverOptions + * 11 cameraDirection */ - (void)takePicture:(CDVInvokedUrlCommand*)command { @@ -122,18 +125,22 @@ - (void)takePicture:(CDVInvokedUrlCommand*)command cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri; if (sourceType == UIImagePickerControllerSourceTypeCamera) { - // we only allow taking pictures (no video) in this api + // We only allow taking pictures (no video) in this API. cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil]; + + // We can only set the camera device if we're actually using the camera. + NSNumber* cameraDirection = [command argumentAtIndex:11 withDefault:[NSNumber numberWithInteger:UIImagePickerControllerCameraDeviceRear]]; + cameraPicker.cameraDevice = (UIImagePickerControllerCameraDevice)[cameraDirection intValue]; } else if (mediaType == MediaTypeAll) { cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType]; } else { - NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie:kUTTypeImage), nil]; + NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage), nil]; cameraPicker.mediaTypes = mediaArray; } if ([self popoverSupported] && (sourceType != UIImagePickerControllerSourceTypeCamera)) { if (cameraPicker.popoverController == nil) { - cameraPicker.popoverController = [[NSClassFromString (@"UIPopoverController")alloc] initWithContentViewController:cameraPicker]; + cameraPicker.popoverController = [[NSClassFromString(@"UIPopoverController")alloc] initWithContentViewController:cameraPicker]; } NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil]; [self displayPopover:options]; @@ -289,12 +296,17 @@ - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingM data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage); } else { data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f); + + /* splice loc */ + CDVJpegHeaderWriter* exifWriter = [[CDVJpegHeaderWriter alloc] init]; + NSString* headerstring = [exifWriter createExifAPP1:[info objectForKey:@"UIImagePickerControllerMediaMetadata"]]; + data = [exifWriter spliceExifBlockIntoJpeg:data withExifBlock:headerstring]; } if (cameraPicker.returnType == DestinationTypeFileUri) { // write to temp directory and return URI // get the temp directory path - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe // generate unique file name @@ -419,7 +431,7 @@ - (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage rotation_radians = 0.0; break; - case UIImageOrientationDown : + case UIImageOrientationDown: rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math break; @@ -518,7 +530,7 @@ - (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSUR // first parameter an image [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"upload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding : NSUTF8StringEncoding]]; [postBody appendData:imageData]; // // second parameter information diff --git a/CordovaLib/Classes/CDVCapture.m b/CordovaLib/Classes/CDVCapture.m index ed9f664..d89e3d3 100755 --- a/CordovaLib/Classes/CDVCapture.m +++ b/CordovaLib/Classes/CDVCapture.m @@ -169,7 +169,7 @@ - (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCal } // write to temp directory and return URI - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; // use file system temporary directory NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -439,7 +439,7 @@ - (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString if ([command isKindOfClass:[CDVFile class]]) { CDVFile* cdvFile = (CDVFile*)command; NSString* mimeType = [cdvFile getMimeTypeFromPath:fullPath]; - [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType:[NSNull null]) forKey:@"type"]; + [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType : [NSNull null]) forKey:@"type"]; } } NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:nil]; @@ -533,7 +533,6 @@ - (NSUInteger)supportedInterfaceOrientations // delegate to CVDAudioRecorderViewController return [self.topViewController supportedInterfaceOrientations]; } - #endif @end @@ -663,7 +662,7 @@ - (void)viewDidLoad // create file to record to in temporary dir - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; // use file system temporary directory NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -701,7 +700,6 @@ - (NSUInteger)supportedInterfaceOrientations orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown); return orientation; } - #endif - (void)viewDidUnload @@ -766,7 +764,7 @@ - (void)stopRecordingCleanup BOOL isUIAccessibilityAnnouncementNotification = (&UIAccessibilityAnnouncementNotification != NULL); if (isUIAccessibilityAnnouncementNotification) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500ull * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ - UIAccessibilityPostNotification (UIAccessibilityAnnouncementNotification, NSLocalizedString (@"timed recording complete", nil)); + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, NSLocalizedString(@"timed recording complete", nil)); }); } } else { diff --git a/CordovaLib/Classes/CDVCommandDelegate.h b/CordovaLib/Classes/CDVCommandDelegate.h index e177c63..0401136 100755 --- a/CordovaLib/Classes/CDVCommandDelegate.h +++ b/CordovaLib/Classes/CDVCommandDelegate.h @@ -30,7 +30,6 @@ - (NSString*)pathForResource:(NSString*)resourcepath; - (id)getCommandInstance:(NSString*)pluginName; -- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className CDV_DEPRECATED(2.2, "Use CDVViewController to register plugins, or use config.xml."); // Plugins should not be using this interface to call other plugins since it // will result in bogus callbacks being made. diff --git a/CordovaLib/Classes/CDVCommandDelegateImpl.m b/CordovaLib/Classes/CDVCommandDelegateImpl.m index e399289..fa0e5e0 100755 --- a/CordovaLib/Classes/CDVCommandDelegateImpl.m +++ b/CordovaLib/Classes/CDVCommandDelegateImpl.m @@ -55,7 +55,11 @@ - (NSString*)pathForResource:(NSString*)resourcepath - (void)evalJsHelper2:(NSString*)js { + CDV_EXEC_LOG(@"Exec: evalling: %@", [js substringToIndex:MIN([js length], 160)]); NSString* commandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString:js]; + if ([commandsJSON length] > 0) { + CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by chaining."); + } [_commandQueue enqueCommandBatch:commandsJSON]; } @@ -78,21 +82,16 @@ - (void)evalJsHelper:(NSString*)js - (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId { + CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status); // This occurs when there is are no win/fail callbacks for the call. - if ([@"INVALID" isEqualToString:callbackId]) { + if ([@"INVALID" isEqualToString : callbackId]) { return; } int status = [result.status intValue]; BOOL keepCallback = [result.keepCallback boolValue]; - id message = result.message == nil ? [NSNull null] : result.message; + NSString* argumentsAsJSON = [result argumentsAsJSON]; - // Use an array to encode the message as JSON. - message = [NSArray arrayWithObject:message]; - NSString* encodedMessage = [message JSONString]; - // And then strip off the outer []s. - encodedMessage = [encodedMessage substringWithRange:NSMakeRange(1, [encodedMessage length] - 2)]; - NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", - callbackId, status, encodedMessage, keepCallback]; + NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback]; [self evalJsHelper:js]; } @@ -122,11 +121,6 @@ - (id)getCommandInstance:(NSString*)pluginName return [_viewController getCommandInstance:pluginName]; } -- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className -{ - [_viewController registerPlugin:plugin withClassName:className]; -} - - (void)runInBackground:(void (^)())block { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); diff --git a/CordovaLib/Classes/CDVCommandQueue.h b/CordovaLib/Classes/CDVCommandQueue.h index ebdf844..27c47b5 100755 --- a/CordovaLib/Classes/CDVCommandQueue.h +++ b/CordovaLib/Classes/CDVCommandQueue.h @@ -22,13 +22,7 @@ @class CDVInvokedUrlCommand; @class CDVViewController; -@interface CDVCommandQueue : NSObject { - @private - NSInteger _lastCommandQueueFlushRequestId; - __weak CDVViewController* _viewController; - NSMutableArray* _queue; - BOOL _currentlyExecuting; -} +@interface CDVCommandQueue : NSObject @property (nonatomic, readonly) BOOL currentlyExecuting; diff --git a/CordovaLib/Classes/CDVCommandQueue.m b/CordovaLib/Classes/CDVCommandQueue.m index a8a58b7..1a0dfa0 100755 --- a/CordovaLib/Classes/CDVCommandQueue.m +++ b/CordovaLib/Classes/CDVCommandQueue.m @@ -23,6 +23,14 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVViewController.h" #import "CDVCommandDelegateImpl.h" +@interface CDVCommandQueue () { + NSInteger _lastCommandQueueFlushRequestId; + __weak CDVViewController* _viewController; + NSMutableArray* _queue; + BOOL _currentlyExecuting; +} +@end + @implementation CDVCommandQueue @synthesize currentlyExecuting = _currentlyExecuting; @@ -74,6 +82,9 @@ - (void)fetchCommandsFromJs @"cordova.require('cordova/exec').nativeFetchMessages()"]; [self enqueCommandBatch:queuedCommandsJSON]; + if ([queuedCommandsJSON length] > 0) { + CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by request."); + } } - (void)executePending @@ -92,13 +103,15 @@ - (void)executePending // Iterate over and execute all of the commands. for (NSArray* jsonEntry in commandBatch) { CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; + CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName); + if (![self execute:command]) { #ifdef DEBUG NSString* commandJson = [jsonEntry JSONString]; static NSUInteger maxLogLength = 1024; NSString* commandString = ([commandJson length] > maxLogLength) ? - [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : - commandJson; + [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : + commandJson; DLog(@"FAILED pluginJSON = %@", commandString); #endif diff --git a/CordovaLib/Classes/CDVConfigParser.m b/CordovaLib/Classes/CDVConfigParser.m index 6fd5913..ffc8ede 100755 --- a/CordovaLib/Classes/CDVConfigParser.m +++ b/CordovaLib/Classes/CDVConfigParser.m @@ -52,7 +52,7 @@ - (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName names } else if ([elementName isEqualToString:@"plugin"]) { NSString* name = [attributeDict[@"name"] lowercaseString]; pluginsDict[name] = attributeDict[@"value"]; - if ([@"true" isEqualToString:attributeDict[@"onload"]]) { + if ([@"true" isEqualToString : attributeDict[@"onload"]]) { [self.startupPluginNames addObject:name]; } } else if ([elementName isEqualToString:@"access"]) { diff --git a/CordovaLib/Classes/CDVConnection.m b/CordovaLib/Classes/CDVConnection.m index 3030711..b3f5cab 100755 --- a/CordovaLib/Classes/CDVConnection.m +++ b/CordovaLib/Classes/CDVConnection.m @@ -52,7 +52,8 @@ - (NSString*)w3cConnectionTypeFor:(CDVReachability*)reachability return @"none"; case ReachableViaWWAN: - return @"2g"; // no generic default, so we use the lowest common denominator + // Return value of '2g' is deprecated as of 2.6.0 and will be replaced with 'cellular' in 3.0.0 + return @"2g"; case ReachableViaWiFi: return @"wifi"; @@ -66,7 +67,8 @@ - (BOOL)isCellularConnection:(NSString*)theConnectionType { return [theConnectionType isEqualToString:@"2g"] || [theConnectionType isEqualToString:@"3g"] || - [theConnectionType isEqualToString:@"4g"]; + [theConnectionType isEqualToString:@"4g"] || + [theConnectionType isEqualToString:@"cellular"]; } - (void)updateReachability:(CDVReachability*)reachability @@ -111,6 +113,7 @@ - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView self.internetReach = [CDVReachability reachabilityForInternetConnection]; self.connectionType = [self w3cConnectionTypeFor:self.internetReach]; [self.internetReach startNotifier]; + [self printDeprecationNotice]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateConnectionType:) name:kReachabilityChangedNotification object:nil]; if (&UIApplicationDidEnterBackgroundNotification && &UIApplicationWillEnterForegroundNotification) { @@ -121,4 +124,9 @@ - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView return self; } +- (void)printDeprecationNotice +{ + NSLog(@"DEPRECATION NOTICE: The Connection ReachableViaWWAN return value of '2g' is deprecated as of Cordova version 2.6.0 and will be changed to 'cellular' in a future release. "); +} + @end diff --git a/CordovaLib/Classes/CDVContact.m b/CordovaLib/Classes/CDVContact.m index 9efaf10..3844525 100755 --- a/CordovaLib/Classes/CDVContact.m +++ b/CordovaLib/Classes/CDVContact.m @@ -38,7 +38,9 @@ - (id)init if ((self = [super init]) != nil) { ABRecordRef rec = ABPersonCreate(); self.record = rec; - CFRelease(rec); + if (rec) { + CFRelease(rec); + } } return self; } @@ -167,9 +169,9 @@ + (NSDictionary*)defaultObjectAndProperties if (org_apache_cordova_contacts_objectAndProperties == nil) { org_apache_cordova_contacts_objectAndProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSArray arrayWithObjects:kW3ContactGivenName, kW3ContactFamilyName, - kW3ContactMiddleName, kW3ContactHonorificPrefix, kW3ContactHonorificSuffix, kW3ContactFormattedName, nil], kW3ContactName, + kW3ContactMiddleName, kW3ContactHonorificPrefix, kW3ContactHonorificSuffix, kW3ContactFormattedName, nil], kW3ContactName, [NSArray arrayWithObjects:kW3ContactStreetAddress, kW3ContactLocality, kW3ContactRegion, - kW3ContactPostalCode, kW3ContactCountry, /*kW3ContactAddressFormatted,*/ nil], kW3ContactAddresses, + kW3ContactPostalCode, kW3ContactCountry, /*kW3ContactAddressFormatted,*/ nil], kW3ContactAddresses, [NSArray arrayWithObjects:kW3ContactOrganizationName, kW3ContactTitle, kW3ContactDepartment, nil], kW3ContactOrganizations, [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactPhoneNumbers, [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactEmails, @@ -228,7 +230,7 @@ - (bool)setFromContactDict:(NSDictionary*)aContact asUpdate:(BOOL)bUpdate NSArray* propArray = [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactName]; for (id i in propArray) { - if (![(NSString*) i isEqualToString:kW3ContactFormattedName]) { // kW3ContactFormattedName is generated from ABRecordCopyCompositeName() and can't be set + if (![(NSString*)i isEqualToString : kW3ContactFormattedName]) { // kW3ContactFormattedName is generated from ABRecordCopyCompositeName() and can't be set [self setValue:[dict valueForKey:i] forProperty:(ABPropertyID)[(NSNumber*)[[CDVContact defaultW3CtoAB] objectForKey:i] intValue] inRecord:person asUpdate:bUpdate]; } @@ -298,9 +300,9 @@ - (bool)setFromContactDict:(NSDictionary*)aContact asUpdate:(BOOL)bUpdate bRemove = YES; } if ([dict isKindOfClass:[NSDictionary class]] || (bRemove == YES)) { - [self setValue:(bRemove ? @"":[dict valueForKey:@"name"]) forProperty:kABPersonOrganizationProperty inRecord:person asUpdate:bUpdate]; - [self setValue:(bRemove ? @"":[dict valueForKey:kW3ContactTitle]) forProperty:kABPersonJobTitleProperty inRecord:person asUpdate:bUpdate]; - [self setValue:(bRemove ? @"":[dict valueForKey:kW3ContactDepartment]) forProperty:kABPersonDepartmentProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:@"name"]) forProperty:kABPersonOrganizationProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:kW3ContactTitle]) forProperty:kABPersonJobTitleProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:kW3ContactDepartment]) forProperty:kABPersonDepartmentProperty inRecord:person asUpdate:bUpdate]; } } // add dates @@ -658,7 +660,7 @@ - (bool)setMultiValueDictionary:(NSArray*)array forProperty:(ABPropertyID)prop i [dict setObject:setValue forKey:(NSString*)[[CDVContact defaultW3CtoAB] valueForKey:(NSString*)k]]; } else if ((value == nil) || ([value isKindOfClass:[NSString class]] && ([value length] != 0))) { // value not provided in contact dictionary - if prop exists in AB dictionary, preserve it - valueAB = [(__bridge NSDictionary*) existingDictionary valueForKey:[[CDVContact defaultW3CtoAB] valueForKey:k]]; + valueAB = [(__bridge NSDictionary*)existingDictionary valueForKey : [[CDVContact defaultW3CtoAB] valueForKey:k]]; if (valueAB != nil) { [dict setValue:valueAB forKey:[[CDVContact defaultW3CtoAB] valueForKey:k]]; } @@ -893,7 +895,7 @@ - (NSDictionary*)toDictionary:(NSDictionary*)withFields if (data != nil) { [nc setObject:data forKey:kW3ContactName]; } - if ([self.returnFields objectForKey:kW3ContactDisplayName] && ((data == nil) || ([(NSDictionary*) data objectForKey:kW3ContactFormattedName] == [NSNull null]))) { + if ([self.returnFields objectForKey:kW3ContactDisplayName] && ((data == nil) || ([(NSDictionary*)data objectForKey : kW3ContactFormattedName] == [NSNull null]))) { // user asked for displayName which iOS doesn't support but there is no other name data being returned // try and use Composite Name so some name is returned id tryName = (__bridge_transfer NSString*)ABRecordCopyCompositeName(self.record); @@ -1114,7 +1116,7 @@ - (NSObject*)extractMultiValue:(NSString*)propertyId // always set id value = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; [newDict setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFieldId]; - [(NSMutableArray*) valuesArray addObject:newDict]; + [(NSMutableArray*)valuesArray addObject : newDict]; } } else { valuesArray = [NSNull null]; @@ -1188,7 +1190,7 @@ - (NSObject*)extractAddresses } if ([newAddress count] > 0) { // ?? this will always be true since we set id,label,primary field?? - [(NSMutableArray*) addresses addObject:newAddress]; + [(NSMutableArray*)addresses addObject : newAddress]; } CFRelease(dict); } // end of loop through addresses @@ -1244,7 +1246,7 @@ - (NSObject*)extractIms bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageServiceKey, (void*)&value); if (bFound && (value != NULL)) { CFRetain(value); - [newDict setObject:(id)[[CDVContact class] convertPropertyLabelToContactType:(__bridge NSString*)value] forKey:kW3ContactFieldType]; + [newDict setObject:(id)[[CDVContact class] convertPropertyLabelToContactType : (__bridge NSString*)value] forKey:kW3ContactFieldType]; CFRelease(value); } else { [newDict setObject:[NSNull null] forKey:kW3ContactFieldType]; @@ -1254,7 +1256,7 @@ - (NSObject*)extractIms id identifier = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; [newDict setObject:(identifier != nil) ? identifier:[NSNull null] forKey:kW3ContactFieldId]; - [(NSMutableArray*) imArray addObject:newDict]; + [(NSMutableArray*)imArray addObject : newDict]; CFRelease(dict); } } else { @@ -1308,7 +1310,7 @@ - (NSObject*)extractOrganizations [newDict setObject:@"false" forKey:kW3ContactFieldPrimary]; [newDict setObject:[NSNull null] forKey:kW3ContactFieldType]; array = [NSMutableArray arrayWithCapacity:1]; - [(NSMutableArray*) array addObject:newDict]; + [(NSMutableArray*)array addObject : newDict]; } else { array = [NSNull null]; } @@ -1327,7 +1329,7 @@ - (NSObject*)extractPhotos NSData* data = (__bridge NSData*)photoData; // write to temp directory and store URI in photos array // get the temp directory path - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; NSError* err = nil; NSString* filePath = [NSString stringWithFormat:@"%@/photo_XXXXX", docsPath]; char template[filePath.length + 1]; diff --git a/CordovaLib/Classes/CDVContacts.h b/CordovaLib/Classes/CDVContacts.h index 17470c0..0342f5b 100755 --- a/CordovaLib/Classes/CDVContacts.h +++ b/CordovaLib/Classes/CDVContacts.h @@ -24,9 +24,9 @@ #import "CDVContact.h" @interface CDVContacts : CDVPlugin + ABPersonViewControllerDelegate, + ABPeoplePickerNavigationControllerDelegate + > { ABAddressBookRef addressBook; } @@ -63,7 +63,7 @@ - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person; - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person - property :(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue; + property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue; /* * search - searches for contacts. Only person records are currently supported. @@ -140,9 +140,9 @@ - (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code; @end -typedef void (^CDVAddressBookWorkerBlock)( - ABAddressBookRef addressBook, - CDVAddressBookAccessError * error +typedef void (^ CDVAddressBookWorkerBlock)( + ABAddressBookRef addressBook, + CDVAddressBookAccessError* error ); @interface CDVAddressBookHelper : NSObject {} diff --git a/CordovaLib/Classes/CDVContacts.m b/CordovaLib/Classes/CDVContacts.m index 3faf6ba..6cb9f08 100755 --- a/CordovaLib/Classes/CDVContacts.m +++ b/CordovaLib/Classes/CDVContacts.m @@ -77,26 +77,26 @@ - (void)newContact:(CDVInvokedUrlCommand*)command CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error just return (no error callback) - return; - } - CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; - npController.addressBook = addrBook; // a CF retaining assign - CFRelease (addrBook); + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error just return (no error callback) + return; + } + CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; + npController.addressBook = addrBook; // a CF retaining assign + CFRelease(addrBook); - npController.newPersonViewDelegate = self; - npController.callbackId = callbackId; + npController.newPersonViewDelegate = self; + npController.callbackId = callbackId; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; - if ([weakSelf.viewController respondsToSelector:@selector(presentViewController:::)]) { - [weakSelf.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [weakSelf.viewController presentModalViewController:navController animated:YES]; - } - }]; + if ([weakSelf.viewController respondsToSelector:@selector(presentViewController:::)]) { + [weakSelf.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [weakSelf.viewController presentModalViewController:navController animated:YES]; + } + }]; } - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person @@ -130,48 +130,48 @@ - (void)displayContact:(CDVInvokedUrlCommand*)command CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error - return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } - ABRecordRef rec = ABAddressBookGetPersonWithRecordID (addrBook, recordID); - - if (rec) { - CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; - personController.displayedPerson = rec; - personController.personViewDelegate = self; - personController.allowsEditing = NO; + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + ABRecordRef rec = ABAddressBookGetPersonWithRecordID(addrBook, recordID); - // create this so DisplayContactViewController will have a "back" button. - UIViewController* parentController = [[UIViewController alloc] init]; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; + if (rec) { + CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; + personController.displayedPerson = rec; + personController.personViewDelegate = self; + personController.allowsEditing = NO; - [navController pushViewController:personController animated:YES]; + // create this so DisplayContactViewController will have a "back" button. + UIViewController* parentController = [[UIViewController alloc] init]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; - if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { - [self.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [self.viewController presentModalViewController:navController animated:YES]; - } + [navController pushViewController:personController animated:YES]; - if (bEdit) { - // create the editing controller and push it onto the stack - ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; - editPersonController.displayedPerson = rec; - editPersonController.personViewDelegate = self; - editPersonController.allowsEditing = YES; - [navController pushViewController:editPersonController animated:YES]; - } + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:navController animated:YES completion:nil]; } else { - // no record, return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + [self.viewController presentModalViewController:navController animated:YES]; } - CFRelease (addrBook); - }]; + + if (bEdit) { + // create the editing controller and push it onto the stack + ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; + editPersonController.displayedPerson = rec; + editPersonController.personViewDelegate = self; + editPersonController.allowsEditing = YES; + [navController pushViewController:editPersonController animated:YES]; + } + } else { + // no record, return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + CFRelease(addrBook); + }]; } - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person @@ -284,95 +284,95 @@ - (void)search:(CDVInvokedUrlCommand*)command NSDictionary* findOptions = [command.arguments objectAtIndex:1 withDefault:[NSNull null]]; [self.commandDelegate runInBackground:^{ - // from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread. - // which is why address book is created within the dispatch queue. - // more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/ - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - // it gets uglier, block within block..... - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error - return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } + // from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread. + // which is why address book is created within the dispatch queue. + // more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/ + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + // it gets uglier, block within block..... + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } - NSArray* foundRecords = nil; - // get the findOptions values - BOOL multiple = NO; // default is false - NSString* filter = nil; - if (![findOptions isKindOfClass:[NSNull class]]) { - id value = nil; - filter = (NSString*)[findOptions objectForKey:@"filter"]; - value = [findOptions objectForKey:@"multiple"]; - if ([value isKindOfClass:[NSNumber class]]) { - // multiple is a boolean that will come through as an NSNumber - multiple = [(NSNumber*) value boolValue]; - // NSLog(@"multiple is: %d", multiple); - } - } + NSArray* foundRecords = nil; + // get the findOptions values + BOOL multiple = NO; // default is false + NSString* filter = nil; + if (![findOptions isKindOfClass:[NSNull class]]) { + id value = nil; + filter = (NSString*)[findOptions objectForKey:@"filter"]; + value = [findOptions objectForKey:@"multiple"]; + if ([value isKindOfClass:[NSNumber class]]) { + // multiple is a boolean that will come through as an NSNumber + multiple = [(NSNumber*)value boolValue]; + // NSLog(@"multiple is: %d", multiple); + } + } - NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; - - NSMutableArray* matches = nil; - if (!filter || [filter isEqualToString:@""]) { - // get all records - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - if (foundRecords && [foundRecords count] > 0) { - // create Contacts and put into matches array - // doesn't make sense to ask for all records when multiple == NO but better check - int xferCount = multiple == YES ? [foundRecords count]:1; - matches = [NSMutableArray arrayWithCapacity:xferCount]; - - for (int k = 0; k < xferCount; k++) { - CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:k]]; - [matches addObject:xferContact]; - xferContact = nil; - } - } - } else { - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - matches = [NSMutableArray arrayWithCapacity:1]; - BOOL bFound = NO; - int testCount = [foundRecords count]; - - for (int j = 0; j < testCount; j++) { - CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]]; - if (testContact) { - bFound = [testContact foundValue:filter inFields:returnFields]; - if (bFound) { - [matches addObject:testContact]; - } - testContact = nil; - } - } + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + + NSMutableArray* matches = nil; + if (!filter || [filter isEqualToString:@""]) { + // get all records + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook); + if (foundRecords && ([foundRecords count] > 0)) { + // create Contacts and put into matches array + // doesn't make sense to ask for all records when multiple == NO but better check + int xferCount = multiple == YES ? [foundRecords count] : 1; + matches = [NSMutableArray arrayWithCapacity:xferCount]; + + for (int k = 0; k < xferCount; k++) { + CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:k]]; + [matches addObject:xferContact]; + xferContact = nil; } - NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; - - if (matches != nil && [matches count] > 0) { - // convert to JS Contacts format and return in callback - // - returnFields determines what properties to return - @autoreleasepool { - int count = multiple == YES ? [matches count]:1; - - for (int i = 0; i < count; i++) { - CDVContact* newContact = [matches objectAtIndex:i]; - NSDictionary* aContact = [newContact toDictionary:returnFields]; - [returnContacts addObject:aContact]; - } + } + } else { + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook); + matches = [NSMutableArray arrayWithCapacity:1]; + BOOL bFound = NO; + int testCount = [foundRecords count]; + + for (int j = 0; j < testCount; j++) { + CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]]; + if (testContact) { + bFound = [testContact foundValue:filter inFields:returnFields]; + if (bFound) { + [matches addObject:testContact]; } + testContact = nil; } - // return found contacts (array is empty if no contacts found) - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - // NSLog(@"findCallback string: %@", jsString); - - if (addrBook) { - CFRelease (addrBook); + } + } + NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; + + if ((matches != nil) && ([matches count] > 0)) { + // convert to JS Contacts format and return in callback + // - returnFields determines what properties to return + @autoreleasepool { + int count = multiple == YES ? [matches count] : 1; + + for (int i = 0; i < count; i++) { + CDVContact* newContact = [matches objectAtIndex:i]; + NSDictionary* aContact = [newContact toDictionary:returnFields]; + [returnContacts addObject:aContact]; } - }]; - }]; // end of workQueue block + } + } + // return found contacts (array is empty if no contacts found) + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + // NSLog(@"findCallback string: %@", jsString); + + if (addrBook) { + CFRelease(addrBook); + } + }]; + }]; // end of workQueue block return; } @@ -383,81 +383,10 @@ - (void)save:(CDVInvokedUrlCommand*)command NSDictionary* contactDict = [command.arguments objectAtIndex:0]; [self.commandDelegate runInBackground:^{ - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { - CDVPluginResult* result = nil; - if (addrBook == NULL) { - // permission was denied or other error - return error - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } - - bool bIsError = FALSE, bSuccess = FALSE; - BOOL bUpdate = NO; - CDVContactError errCode = UNKNOWN_ERROR; - CFErrorRef error; - NSNumber* cId = [contactDict valueForKey:kW3ContactId]; - CDVContact* aContact = nil; - ABRecordRef rec = nil; - if (cId && ![cId isKindOfClass:[NSNull class]]) { - rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); - if (rec) { - aContact = [[CDVContact alloc] initFromABRecord:rec]; - bUpdate = YES; - } - } - if (!aContact) { - aContact = [[CDVContact alloc] init]; - } - - bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; - if (bSuccess) { - if (!bUpdate) { - bSuccess = ABAddressBookAddRecord (addrBook, [aContact record], &error); - } - if (bSuccess) { - bSuccess = ABAddressBookSave (addrBook, &error); - } - if (!bSuccess) { // need to provide error codes - bIsError = TRUE; - errCode = IO_ERROR; - } else { - // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified - // so would give back all fields (which W3C spec. indicates is not desired) - // for now (while testing) give back saved, full contact - NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; - // NSString* contactStr = [newContact JSONRepresentation]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; - } - } else { - bIsError = TRUE; - errCode = IO_ERROR; - } - CFRelease (addrBook); - - if (bIsError) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; - } - - if (result) { - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - } - }]; - }]; // end of queue -} - -- (void)remove:(CDVInvokedUrlCommand*)command -{ - NSString* callbackId = command.callbackId; - NSNumber* cId = [command.arguments objectAtIndex:0]; - - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errorCode) { CDVPluginResult* result = nil; if (addrBook == NULL) { // permission was denied or other error - return error @@ -467,50 +396,121 @@ - (void)remove:(CDVInvokedUrlCommand*)command } bool bIsError = FALSE, bSuccess = FALSE; + BOOL bUpdate = NO; CDVContactError errCode = UNKNOWN_ERROR; CFErrorRef error; + NSNumber* cId = [contactDict valueForKey:kW3ContactId]; + CDVContact* aContact = nil; ABRecordRef rec = nil; - if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { - rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); + if (cId && ![cId isKindOfClass:[NSNull class]]) { + rec = ABAddressBookGetPersonWithRecordID(addrBook, [cId intValue]); if (rec) { - bSuccess = ABAddressBookRemoveRecord (addrBook, rec, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - bSuccess = ABAddressBookSave (addrBook, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - // set id to null - // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; - // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - // NSString* contactStr = [contactDict JSONRepresentation]; - } - } - } else { - // no record found return error + aContact = [[CDVContact alloc] initFromABRecord:rec]; + bUpdate = YES; + } + } + if (!aContact) { + aContact = [[CDVContact alloc] init]; + } + + bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; + if (bSuccess) { + if (!bUpdate) { + bSuccess = ABAddressBookAddRecord(addrBook, [aContact record], &error); + } + if (bSuccess) { + bSuccess = ABAddressBookSave(addrBook, &error); + } + if (!bSuccess) { // need to provide error codes bIsError = TRUE; - errCode = UNKNOWN_ERROR; + errCode = IO_ERROR; + } else { + // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified + // so would give back all fields (which W3C spec. indicates is not desired) + // for now (while testing) give back saved, full contact + NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; + // NSString* contactStr = [newContact JSONRepresentation]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; } } else { - // invalid contact id provided bIsError = TRUE; - errCode = INVALID_ARGUMENT_ERROR; + errCode = IO_ERROR; } + CFRelease(addrBook); - if (addrBook) { - CFRelease (addrBook); - } if (bIsError) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; } + if (result) { [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; } }]; + }]; // end of queue +} + +- (void)remove:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSNumber* cId = [command.arguments objectAtIndex:0]; + + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errorCode) { + CDVPluginResult* result = nil; + if (addrBook == NULL) { + // permission was denied or other error - return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + + bool bIsError = FALSE, bSuccess = FALSE; + CDVContactError errCode = UNKNOWN_ERROR; + CFErrorRef error; + ABRecordRef rec = nil; + if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { + rec = ABAddressBookGetPersonWithRecordID(addrBook, [cId intValue]); + if (rec) { + bSuccess = ABAddressBookRemoveRecord(addrBook, rec, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + bSuccess = ABAddressBookSave(addrBook, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + // set id to null + // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; + // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + // NSString* contactStr = [contactDict JSONRepresentation]; + } + } + } else { + // no record found return error + bIsError = TRUE; + errCode = UNKNOWN_ERROR; + } + } else { + // invalid contact id provided + bIsError = TRUE; + errCode = INVALID_ARGUMENT_ERROR; + } + + if (addrBook) { + CFRelease(addrBook); + } + if (bIsError) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + if (result) { + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + }]; return; } @@ -569,24 +569,24 @@ - (void)createAddressBook:(CDVAddressBookWorkerBlock)workerBlock addressBook = ABAddressBookCreateWithOptions(NULL, &error); // NSLog(@"addressBook access: %lu", status); ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { - // callback can occur in background, address book must be accessed on thread it was created on - dispatch_sync (dispatch_get_main_queue (), ^{ + // callback can occur in background, address book must be accessed on thread it was created on + dispatch_sync(dispatch_get_main_queue(), ^{ if (error) { - workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + workerBlock(NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); } else if (!granted) { - workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); + workerBlock(NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); } else { // access granted - workerBlock (addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + workerBlock(addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); } }); - }); + }); } else #endif { // iOS 4 or 5 no checks needed - addressBook = ABAddressBookCreate (); - workerBlock (addressBook, NULL); + addressBook = ABAddressBookCreate(); + workerBlock(addressBook, NULL); } } diff --git a/CordovaLib/Classes/CDVDebugConsole.m b/CordovaLib/Classes/CDVDebugConsole.m deleted file mode 100755 index 29cbb91..0000000 --- a/CordovaLib/Classes/CDVDebugConsole.m +++ /dev/null @@ -1,37 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -#import "CDVDebugConsole.h" - -@implementation CDVDebugConsole - -- (void)log:(CDVInvokedUrlCommand*)command -{ - NSString* message = [command.arguments objectAtIndex:0]; - NSDictionary* options = [command.arguments objectAtIndex:1]; - NSString* log_level = @"INFO"; - - if ([options objectForKey:@"logLevel"]) { - log_level = [options objectForKey:@"logLevel"]; - } - - NSLog(@"[%@] %@", log_level, message); -} - -@end diff --git a/CordovaLib/Classes/CDVEcho.m b/CordovaLib/Classes/CDVEcho.m index 916e315..c74990d 100755 --- a/CordovaLib/Classes/CDVEcho.m +++ b/CordovaLib/Classes/CDVEcho.m @@ -51,4 +51,11 @@ - (void)echoArrayBuffer:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)echoMultiPart:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsMultipart:command.arguments]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + @end diff --git a/CordovaLib/Classes/CDVExif.h b/CordovaLib/Classes/CDVExif.h new file mode 100755 index 0000000..3e8adbd --- /dev/null +++ b/CordovaLib/Classes/CDVExif.h @@ -0,0 +1,43 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#ifndef CordovaLib_ExifData_h +#define CordovaLib_ExifData_h + +// exif data types +typedef enum exifDataTypes { + EDT_UBYTE = 1, // 8 bit unsigned integer + EDT_ASCII_STRING, // 8 bits containing 7 bit ASCII code, null terminated + EDT_USHORT, // 16 bit unsigned integer + EDT_ULONG, // 32 bit unsigned integer + EDT_URATIONAL, // 2 longs, first is numerator and second is denominator + EDT_SBYTE, + EDT_UNDEFINED, // 8 bits + EDT_SSHORT, + EDT_SLONG, // 32bit signed integer (2's complement) + EDT_SRATIONAL, // 2 SLONGS, first long is numerator, second is denominator + EDT_SINGLEFLOAT, + EDT_DOUBLEFLOAT +} ExifDataTypes; + +// maps integer code for exif data types to width in bytes +static const int DataTypeToWidth[] = {1,1,2,4,8,1,1,2,4,8,4,8}; + +static const int RECURSE_HORIZON = 8; +#endif diff --git a/CordovaLib/Classes/CDVFile.h b/CordovaLib/Classes/CDVFile.h index 4862921..eaf8cbe 100755 --- a/CordovaLib/Classes/CDVFile.h +++ b/CordovaLib/Classes/CDVFile.h @@ -21,6 +21,7 @@ #import "CDVPlugin.h" enum CDVFileError { + NO_ERROR = 0, NOT_FOUND_ERR = 1, SECURITY_ERR = 2, ABORT_ERR = 3, @@ -76,6 +77,7 @@ extern NSString* const kCDVAssetsLibraryPrefix; - (void)readAsText:(CDVInvokedUrlCommand*)command; - (void)readAsDataURL:(CDVInvokedUrlCommand*)command; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command; - (NSString*)getMimeTypeFromPath:(NSString*)fullPath; - (void)write:(CDVInvokedUrlCommand*)command; - (void)testFileExists:(CDVInvokedUrlCommand*)command; diff --git a/CordovaLib/Classes/CDVFile.m b/CordovaLib/Classes/CDVFile.m index d52405d..10908ce 100755 --- a/CordovaLib/Classes/CDVFile.m +++ b/CordovaLib/Classes/CDVFile.m @@ -52,7 +52,7 @@ - (id)initWithWebView:(UIWebView*)theWebView paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); self.appLibraryPath = [paths objectAtIndex:0]; - self.appTempPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() + self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]]; self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]]; @@ -164,7 +164,7 @@ - (void)requestFileSystem:(CDVInvokedUrlCommand*)command result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; } else { NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; - [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary:kW3FilePersistent) forKey:@"name"]; + [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary : kW3FilePersistent) forKey:@"name"]; NSDictionary* dirEntry = [self getDirectoryEntry:fullPath isDirectory:YES]; [fileSystem setObject:dirEntry forKey:@"root"]; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; @@ -227,10 +227,9 @@ - (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command NSURL* testUri = [NSURL URLWithString:strUri]; CDVPluginResult* result = nil; - if (!testUri || ![testUri isFileURL]) { - // issue ENCODING_ERR + if (!testUri) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; - } else { + } else if ([testUri isFileURL]) { NSFileManager* fileMgr = [[NSFileManager alloc] init]; NSString* path = [testUri path]; // NSLog(@"url path: %@", path); @@ -262,7 +261,13 @@ - (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command // return NOT_FOUND_ERR result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; } + } else if ([strUri hasPrefix:@"assets-library://"]) { + NSDictionary* fileSystem = [self getDirectoryEntry:strUri isDirectory:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; } + if (result != nil) { [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @@ -486,7 +491,7 @@ - (void)getMetadata:(CDVInvokedUrlCommand*)command // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Retrieve the metadata and send it off. NSDate* date = [asset valueForProperty:ALAssetPropertyDate]; @@ -499,7 +504,7 @@ - (void)getMetadata:(CDVInvokedUrlCommand*)command } }; // TODO(maxw): Consider making this a class variable since it's the same every time. - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -525,7 +530,7 @@ - (void)getMetadata:(CDVInvokedUrlCommand*)command } else { // didn't get fileAttribs CDVFileError errorCode = ABORT_ERR; - NSLog (@"error getting metadata: %@", [error localizedDescription]); + NSLog(@"error getting metadata: %@", [error localizedDescription]); if ([error code] == NSFileNoSuchFileError) { errorCode = NOT_FOUND_ERR; } @@ -779,7 +784,7 @@ - (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and try to copy it over. if (![fileMgr fileExistsAtPath:destRootPath]) { @@ -796,7 +801,7 @@ - (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy // We're good to go! Write the file to the new destination. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [data writeToFile:newFullPath atomically:YES]; @@ -808,7 +813,7 @@ - (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -953,7 +958,7 @@ - (void)getFileMetadata:(CDVInvokedUrlCommand*)command // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Populate the dictionary and send it off. NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; @@ -975,7 +980,7 @@ - (void)getFileMetadata:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -1053,154 +1058,181 @@ - (void)readEntries:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } +- (void)readFileWithPath:(NSString*)path start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback +{ + if (path == nil) { + callback(nil, nil, SYNTAX_ERR); + } else { + [self.commandDelegate runInBackground:^ { + if ([path hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + + callback(data, MIMEType, NO_ERROR); + } else { + callback(nil, nil, NOT_FOUND_ERR); + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + NSLog(@"Error: %@", error); + callback(nil, nil, SECURITY_ERR); + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:path] resultBlock:resultBlock failureBlock:failureBlock]; + } else { + NSString* mimeType = [self getMimeTypeFromPath:path]; + if (mimeType == nil) { + mimeType = @"*/*"; + } + NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path]; + if (start > 0) { + [file seekToFileOffset:start]; + } + + NSData* readData; + if (end < 0) { + readData = [file readDataToEndOfFile]; + } else { + readData = [file readDataOfLength:(end - start)]; + } + + [file closeFile]; + + callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR); + } + }]; + } +} + /* read and return file data * IN: * NSArray* arguments * 0 - NSString* fullPath - * 1 - NSString* encoding - NOT USED, iOS reads and writes using UTF8! - * 2 - NSString* start - OPTIONAL, only provided when not == 0. - * 3 - NSString* end - OPTIONAL, only provided when not == length. + * 1 - NSString* encoding + * 2 - NSString* start + * 3 - NSString* end */ - (void)readAsText:(CDVInvokedUrlCommand*)command { // arguments - NSString* argPath = [command.arguments objectAtIndex:0]; - NSInteger start = 0; - NSInteger end = -1; - - if ([command.arguments count] >= 3) { - start = [[command.arguments objectAtIndex:2] integerValue]; - } - if ([command.arguments count] >= 4) { - end = [[command.arguments objectAtIndex:3] integerValue]; + NSString* path = [command argumentAtIndex:0]; + NSString* encoding = [command argumentAtIndex:1]; + NSInteger start = [[command argumentAtIndex:2] integerValue]; + NSInteger end = [[command argumentAtIndex:3] integerValue]; + + // TODO: implement + if (![@"UTF-8" isEqualToString : encoding]) { + NSLog(@"Only UTF-8 encodings are currently supported by readAsText"); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; } - // NSString* encoding = [command.arguments objectAtIndex:2]; // not currently used - CDVPluginResult* result = nil; - - NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - - if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { - // can't read assets-library URLs as text - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; - } else if (!file) { - // invalid path entry - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; - } else { - if (start > 0) { - [file seekToFileOffset:start]; - } - - NSData* readData; - if (end < 0) { - readData = [file readDataToEndOfFile]; - } else { - readData = [file readDataOfLength:(end - start)]; + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO]; + // Check that UTF8 conversion did not fail. + if (str != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str]; + result.associatedObject = data; + } else { + errorCode = ENCODING_ERR; + } } - - [file closeFile]; - NSString* pNStrBuff = nil; - if (readData) { - pNStrBuff = [[NSString alloc] initWithBytes:[readData bytes] length:[readData length] encoding:NSUTF8StringEncoding]; - } else { - // return empty string if no data - pNStrBuff = @""; + if (result == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; } - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:pNStrBuff]; - } - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; } /* Read content of text file and return as base64 encoded data url. * IN: * NSArray* arguments * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end * * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined. */ - (void)readAsDataURL:(CDVInvokedUrlCommand*)command { - // arguments - NSString* argPath = [command.arguments objectAtIndex:0]; - NSInteger start = 0; - NSInteger end = -1; - - if ([command.arguments count] >= 2) { - start = [[command.arguments objectAtIndex:1] integerValue]; - } - if ([command.arguments count] >= 3) { - end = [[command.arguments objectAtIndex:2] integerValue]; - } + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + // TODO: Would be faster to base64 encode directly to the final string. + NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } - CDVFileError errCode = ABORT_ERR; - __block CDVPluginResult* result = nil; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} - if (!argPath) { - errCode = SYNTAX_ERR; - } else if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { - // In this case, we need to use an asynchronous method to retrieve the file. - // Because of this, we can't just assign to `result` and send it at the end of the method. - // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { - if (asset) { - // We have the asset! Get the data and send it off. - ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); - NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; - NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; - NSString* mimeType = [self getMimeTypeFromPath:[assetRepresentation filename]]; - NSString* dataString = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:dataString]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - } else { - // We couldn't find the asset. Send the appropriate error. - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - } - }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { - // Retrieving the asset failed for some reason. Send the appropriate error. - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - }; +/* Read content of text file and return as an arraybuffer + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end + */ - ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; - [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; - return; - } else { - NSString* mimeType = [self getMimeTypeFromPath:argPath]; - if (!mimeType) { - // can't return as data URL if can't figure out the mimeType - errCode = ENCODING_ERR; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data]; } else { - NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - if (start > 0) { - [file seekToFileOffset:start]; - } + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } - NSData* readData; - if (end < 0) { - readData = [file readDataToEndOfFile]; - } else { - readData = [file readDataOfLength:(end - start)]; - } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} - [file closeFile]; - if (readData) { - NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [readData base64EncodedString]]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; - } else { - errCode = NOT_FOUND_ERR; - } +- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload]; + result.associatedObject = data; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; } - } - if (!result) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; - } - // NSLog(@"readAsDataURL return: %@", jsString); - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; } /* helper function to get the mimeType from the file extension @@ -1219,7 +1251,7 @@ - (NSString*)getMimeTypeFromPath:(NSString*)fullPath mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType); if (!mimeType) { // special case for m4a - if ([(__bridge NSString*) typeId rangeOfString:@"m4a-audio"].location != NSNotFound) { + if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) { mimeType = @"audio/mp4"; } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) { mimeType = @"audio/wav"; @@ -1343,7 +1375,7 @@ - (void)testFileExists:(CDVInvokedUrlCommand*)command NSString* appFile = argPath; // [ self getFullPath: argPath]; BOOL bExists = [fMgr fileExistsAtPath:appFile]; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1:0)]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @@ -1359,7 +1391,7 @@ - (void)testDirectoryExists:(CDVInvokedUrlCommand*)command BOOL bIsDir = NO; BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir]; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1:0)]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } diff --git a/CordovaLib/Classes/CDVFileTransfer.h b/CordovaLib/Classes/CDVFileTransfer.h index f96bb7d..233a114 100755 --- a/CordovaLib/Classes/CDVFileTransfer.h +++ b/CordovaLib/Classes/CDVFileTransfer.h @@ -48,15 +48,20 @@ extern NSString* const kOptionsKeyCookie; - (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target; - (NSMutableDictionary*)createFileTransferError:(int)code - AndSource :(NSString*)source - AndTarget :(NSString*)target - AndHttpStatus :(int)httpStatus - AndBody :(NSString*)body; + AndSource:(NSString*)source + AndTarget:(NSString*)target + AndHttpStatus:(int)httpStatus + AndBody:(NSString*)body; @property (readonly) NSMutableDictionary* activeTransfers; +@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskID; @end +@class CDVFileTransferEntityLengthRequest; + @interface CDVFileTransferDelegate : NSObject {} +- (void)updateBytesExpected:(NSInteger)newBytesExpected; + @property (strong) NSMutableData* responseData; // atomic @property (nonatomic, strong) CDVFileTransfer* command; @property (nonatomic, assign) CDVFileTransferDirection direction; @@ -70,5 +75,7 @@ extern NSString* const kOptionsKeyCookie; @property (nonatomic, assign) NSInteger bytesTransfered; @property (nonatomic, assign) NSInteger bytesExpected; @property (nonatomic, assign) BOOL trustAllHosts; +@property (strong) NSFileHandle* targetFileHandle; +@property (nonatomic, strong) CDVFileTransferEntityLengthRequest* entityLengthRequest; @end; diff --git a/CordovaLib/Classes/CDVFileTransfer.m b/CordovaLib/Classes/CDVFileTransfer.m index 4ccdce6..5536715 100755 --- a/CordovaLib/Classes/CDVFileTransfer.m +++ b/CordovaLib/Classes/CDVFileTransfer.m @@ -52,8 +52,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) while (totalBytesWritten < bytesToWrite) { CFIndex result = CFWriteStreamWrite(stream, - bytes + totalBytesWritten, - bytesToWrite - totalBytesWritten); + bytes + totalBytesWritten, + bytesToWrite - totalBytesWritten); if (result < 0) { CFStreamError error = CFWriteStreamGetError(stream); NSLog(@"WriteStreamError domain: %ld error: %ld", error.domain, error.error); @@ -126,17 +126,19 @@ - (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData // arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode] // however, params is a JavaScript object and during marshalling is put into the options dict, // thus debug and chunkedMode are the 6th and 7th arguments - NSArray* arguments = command.arguments; - NSString* target = (NSString*)[arguments objectAtIndex:0]; - NSString* server = (NSString*)[arguments objectAtIndex:1]; - NSString* fileKey = [arguments objectAtIndex:2 withDefault:@"file"]; - NSString* fileName = [arguments objectAtIndex:3 withDefault:@"no-filename"]; - NSString* mimeType = [arguments objectAtIndex:4 withDefault:nil]; - NSDictionary* options = [arguments objectAtIndex:5 withDefault:nil]; + NSString* target = [command argumentAtIndex:0]; + NSString* server = [command argumentAtIndex:1]; + NSString* fileKey = [command argumentAtIndex:2 withDefault:@"file"]; + NSString* fileName = [command argumentAtIndex:3 withDefault:@"no-filename"]; + NSString* mimeType = [command argumentAtIndex:4 withDefault:nil]; + NSDictionary* options = [command argumentAtIndex:5 withDefault:nil]; // BOOL trustAllHosts = [[arguments objectAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs - BOOL chunkedMode = [[arguments objectAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; - NSDictionary* headers = [arguments objectAtIndex:8 withDefault:nil]; - + BOOL chunkedMode = [[command argumentAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; + NSDictionary* headers = [command argumentAtIndex:8 withDefault:nil]; + // Allow alternative http method, default to POST. JS side checks + // for allowed methods, currently PUT or POST (forces POST for + // unrecognised values) + NSString* httpMethod = [command argumentAtIndex:10 withDefault:@"POST"]; CDVPluginResult* result = nil; CDVFileTransferError errorCode = 0; @@ -158,7 +160,8 @@ - (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData } NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; - [req setHTTPMethod:@"POST"]; + + [req setHTTPMethod:httpMethod]; // Magic value to set a cookie if ([options objectForKey:kOptionsKeyCookie]) { @@ -190,7 +193,7 @@ - (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData [postBodyBeforeFile appendData:formBoundaryData]; [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; [postBodyBeforeFile appendData:[val dataUsingEncoding:NSUTF8StringEncoding]]; - [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding : NSUTF8StringEncoding]]; } [postBodyBeforeFile appendData:formBoundaryData]; @@ -212,23 +215,29 @@ - (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData CFStreamCreateBoundPair(NULL, &readStream, &writeStream, kStreamBufferSize); [req setHTTPBodyStream:CFBridgingRelease(readStream)]; + self.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID]; + self.backgroundTaskID = UIBackgroundTaskInvalid; + NSLog(@"Background task to upload media finished."); + }]; + [self.commandDelegate runInBackground:^{ - if (CFWriteStreamOpen (writeStream)) { - NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile}; - int numChunks = sizeof (chunks) / sizeof (chunks[0]); - - for (int i = 0; i < numChunks; ++i) { - CFIndex result = WriteDataToStream (chunks[i], writeStream); - if (result <= 0) { - break; - } + if (CFWriteStreamOpen(writeStream)) { + NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile}; + int numChunks = sizeof(chunks) / sizeof(chunks[0]); + + for (int i = 0; i < numChunks; ++i) { + CFIndex result = WriteDataToStream(chunks[i], writeStream); + if (result <= 0) { + break; } - } else { - NSLog (@"FileTransfer: Failed to open writeStream"); } - CFWriteStreamClose (writeStream); - CFRelease (writeStream); - }]; + } else { + NSLog(@"FileTransfer: Failed to open writeStream"); + } + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + }]; } else { [postBodyBeforeFile appendData:fileData]; [postBodyBeforeFile appendData:postBodyAfterFile]; @@ -265,11 +274,11 @@ - (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command // return unsupported result for assets-library URLs if ([target hasPrefix:kCDVAssetsLibraryPrefix]) { // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and send it off. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* fileData = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [self uploadData:fileData command:command]; @@ -279,7 +288,7 @@ - (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -291,12 +300,18 @@ - (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command } else { // Extract the path part out of a file: URL. NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path]; + if (filePath == nil) { + // We couldn't find the asset. Send the appropriate error. + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } // Memory map the file so that it can be read efficiently even if it is large. NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; if (err != nil) { - NSLog (@"Error opening file %@: %@", target, err); + NSLog(@"Error opening file %@: %@", target, err); } [self uploadData:fileData command:command]; } @@ -352,6 +367,7 @@ - (void)download:(CDVInvokedUrlCommand*)command NSString* filePath = [command.arguments objectAtIndex:1]; BOOL trustAllHosts = [[command.arguments objectAtIndex:2 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs NSString* objectId = [command.arguments objectAtIndex:3]; + NSDictionary* headers = [command.arguments objectAtIndex:4 withDefault:nil]; // return unsupported result for assets-library URLs if ([filePath hasPrefix:kCDVAssetsLibraryPrefix]) { @@ -388,7 +404,7 @@ - (void)download:(CDVInvokedUrlCommand*)command } NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; - [self applyRequestHeaders:nil toRequest:req]; + [self applyRequestHeaders:headers toRequest:req]; CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init]; delegate.command = self; @@ -449,19 +465,56 @@ - (void)onReset @end +@interface CDVFileTransferEntityLengthRequest : NSObject { + NSURLConnection* _connection; + CDVFileTransferDelegate* __weak _originalDelegate; +} + +- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate; + +@end; + +@implementation CDVFileTransferEntityLengthRequest; + +- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate +{ + if (self) { + DLog(@"Requesting entity length for GZIPped content..."); + + NSMutableURLRequest* req = [originalRequest mutableCopy]; + [req setHTTPMethod:@"HEAD"]; + [req setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"]; + + _originalDelegate = originalDelegate; + _connection = [NSURLConnection connectionWithRequest:req delegate:self]; + } + return self; +} + +- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response +{ + DLog(@"HEAD request returned; content-length is %lld", [response expectedContentLength]); + [_originalDelegate updateBytesExpected:[response expectedContentLength]]; +} + +- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data +{} + +- (void)connectionDidFinishLoading:(NSURLConnection*)connection +{} + +@end + @implementation CDVFileTransferDelegate -@synthesize callbackId, connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId; +@synthesize callbackId, connection = _connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId, targetFileHandle; - (void)connectionDidFinishLoading:(NSURLConnection*)connection { NSString* uploadResponse = nil; NSString* downloadResponse = nil; - BOOL downloadWriteOK = NO; NSMutableDictionary* uploadResult; CDVPluginResult* result = nil; - NSError* __autoreleasing error = nil; - NSString* parentPath; BOOL bDirRequest = NO; CDVFile* file; @@ -484,40 +537,15 @@ - (void)connectionDidFinishLoading:(NSURLConnection*)connection } } if (self.direction == CDV_TRANSFER_DOWNLOAD) { - DLog(@"Write file %@", target); - // error=[[NSError alloc]init]; + if (self.targetFileHandle) { + [self.targetFileHandle closeFile]; + self.targetFileHandle = nil; + DLog(@"File Transfer Download success"); - if ((self.responseCode >= 200) && (self.responseCode < 300)) { - @try { - parentPath = [self.target stringByDeletingLastPathComponent]; - - // check if the path exists => create directories if needed - if (![[NSFileManager defaultManager] fileExistsAtPath:parentPath]) { - [[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - downloadWriteOK = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error]; - - if (downloadWriteOK == NO) { - // send our results back - downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; - } else { - DLog(@"File Transfer Download success"); - - file = [[CDVFile alloc] init]; - - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]]; - } - } - @catch(id exception) { - // jump back to main thread - downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; - } + file = [[CDVFile alloc] init]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]]; } else { downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; } } @@ -526,11 +554,28 @@ - (void)connectionDidFinishLoading:(NSURLConnection*)connection // remove connection for activeTransfers [command.activeTransfers removeObjectForKey:objectId]; + + // remove background id task in case our upload was done in the background + [[UIApplication sharedApplication] endBackgroundTask:self.command.backgroundTaskID]; + self.command.backgroundTaskID = UIBackgroundTaskInvalid; +} + +- (void)cancelTransferWithError:(NSURLConnection*)connection errorMessage:(NSString*)errorMessage +{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[self.command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:self.source AndTarget:self.target AndHttpStatus:self.responseCode AndBody:errorMessage]]; + + NSLog(@"File Transfer Error: %@", errorMessage); + [connection cancel]; + [self.command.activeTransfers removeObjectForKey:self.objectId]; + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; } - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response { + NSError* __autoreleasing error = nil; + self.mimeType = [response MIMEType]; + self.targetFileHandle = nil; // required for iOS 4.3, for some reason; response is // a plain NSURLResponse, not the HTTP subclass @@ -539,6 +584,11 @@ - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLRespons self.responseCode = [httpResponse statusCode]; self.bytesExpected = [response expectedContentLength]; + if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode == 200) && (self.bytesExpected == NSURLResponseUnknownLength)) { + // Kick off HEAD request to server to get real length + // bytesExpected will be updated when that response is returned + self.entityLengthRequest = [[CDVFileTransferEntityLengthRequest alloc] initWithOriginalRequest:connection.currentRequest andDelegate:self]; + } } else if ([response.URL isFileURL]) { NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[response.URL path] error:nil]; self.responseCode = 200; @@ -547,6 +597,31 @@ - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLRespons self.responseCode = 200; self.bytesExpected = NSURLResponseUnknownLength; } + if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode >= 200) && (self.responseCode < 300)) { + // Download response is okay; begin streaming output to file + NSString* parentPath = [self.target stringByDeletingLastPathComponent]; + + // create parent directories if needed + if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) { + if (error) { + [self cancelTransferWithError:connection errorMessage:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]]]; + } else { + [self cancelTransferWithError:connection errorMessage:@"Could not create path to save downloaded file"]; + } + return; + } + // create target file + if ([[NSFileManager defaultManager] createFileAtPath:self.target contents:nil attributes:nil] == NO) { + [self cancelTransferWithError:connection errorMessage:@"Could not create target file"]; + return; + } + // open target file for writing + self.targetFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.target]; + if (self.targetFileHandle == nil) { + [self cancelTransferWithError:connection errorMessage:@"Could not open target file for writing"]; + } + DLog(@"Streaming to file %@", target); + } } - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error @@ -564,10 +639,30 @@ - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { self.bytesTransfered += data.length; - [self.responseData appendData:data]; + if (self.targetFileHandle) { + [self.targetFileHandle writeData:data]; + } else { + [self.responseData appendData:data]; + } + [self updateProgress]; +} + +- (void)updateBytesExpected:(NSInteger)newBytesExpected +{ + DLog(@"Updating bytesExpected to %d", newBytesExpected); + self.bytesExpected = newBytesExpected; + [self updateProgress]; +} +- (void)updateProgress +{ if (self.direction == CDV_TRANSFER_DOWNLOAD) { BOOL lengthComputable = (self.bytesExpected != NSURLResponseUnknownLength); + // If the response is GZipped, and we have an outstanding HEAD request to get + // the length, then hold off on sending progress events. + if (!lengthComputable && (self.entityLengthRequest != nil)) { + return; + } NSMutableDictionary* downloadProgress = [NSMutableDictionary dictionaryWithCapacity:3]; [downloadProgress setObject:[NSNumber numberWithBool:lengthComputable] forKey:@"lengthComputable"]; [downloadProgress setObject:[NSNumber numberWithInt:self.bytesTransfered] forKey:@"loaded"]; @@ -611,6 +706,7 @@ - (id)init { if ((self = [super init])) { self.responseData = [NSMutableData data]; + self.targetFileHandle = nil; } return self; } diff --git a/CordovaLib/Classes/CDVGlobalization.m b/CordovaLib/Classes/CDVGlobalization.m index 4d960cd..9eb9721 100755 --- a/CordovaLib/Classes/CDVGlobalization.m +++ b/CordovaLib/Classes/CDVGlobalization.m @@ -138,14 +138,14 @@ - (void)dateToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)o // create the formatter using the user's current default locale and formats for dates and times CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // if we have a valid date object then call the formatter if (date) { dateString = (__bridge_transfer NSString*)CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, - formatter, - (__bridge CFDateRef)date); + formatter, + (__bridge CFDateRef)date); } // if the date was converted to a string successfully then return the result @@ -227,18 +227,18 @@ - (void)stringToDate:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)o // get the user's default settings for date and time formats CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // set the parsing to be more lenient CFDateFormatterSetProperty(formatter, kCFDateFormatterIsLenient, kCFBooleanTrue); // parse tha date and time string CFDateRef date = CFDateFormatterCreateDateFromString(kCFAllocatorDefault, - formatter, - (__bridge CFStringRef)dateString, - NULL); + formatter, + (__bridge CFStringRef)dateString, + NULL); // if we were able to parse the date then get the date and time components if (date != NULL) { @@ -336,9 +336,9 @@ - (void)getDatePattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary* // get the user's default settings for date and time formats CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // get the date pattern to apply when formatting and parsing CFStringRef datePattern = CFDateFormatterGetFormat(formatter); @@ -415,9 +415,9 @@ - (void)getDateNames:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)o } CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - kCFDateFormatterFullStyle, - kCFDateFormatterFullStyle); + currentLocale, + kCFDateFormatterFullStyle, + kCFDateFormatterFullStyle); if ((selector == CDV_SELECTOR_MONTHS) && (style == CDV_FORMAT_LONG)) { dataStyle = kCFDateFormatterMonthSymbols; @@ -545,13 +545,13 @@ - (void)numberToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary* } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); // get the localized string based upon the locale and user preferences NSString* numberString = (__bridge_transfer NSString*)CFNumberFormatterCreateStringWithNumber(kCFAllocatorDefault, - formatter, - (__bridge CFNumberRef)number); + formatter, + (__bridge CFNumberRef)number); if (numberString) { NSDictionary* dictionary = [NSDictionary dictionaryWithObject:numberString forKey:@"value"]; @@ -612,8 +612,8 @@ - (void)stringToNumber:(NSMutableArray*)arguments withDict:(NSMutableDictionary* } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); // we need to make this lenient so as to avoid problems with parsing currencies that have non-breaking space characters if (style == kCFNumberFormatterCurrencyStyle) { @@ -622,10 +622,10 @@ - (void)stringToNumber:(NSMutableArray*)arguments withDict:(NSMutableDictionary* // parse againist the largest type to avoid data loss Boolean rc = CFNumberFormatterGetValueFromString(formatter, - (__bridge CFStringRef)numberString, - NULL, - kCFNumberDoubleType, - &doubleValue); + (__bridge CFStringRef)numberString, + NULL, + kCFNumberDoubleType, + &doubleValue); if (rc) { NSDictionary* dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:doubleValue] forKey:@"value"]; @@ -681,8 +681,8 @@ - (void)getNumberPattern:(NSMutableArray*)arguments withDict:(NSMutableDictionar } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); NSString* numberPattern = (__bridge NSString*)CFNumberFormatterGetFormat(formatter); @@ -749,8 +749,8 @@ - (void)getCurrencyPattern:(NSMutableArray*)arguments withDict:(NSMutableDiction // now set the currency code in the formatter if (rc) { CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - kCFNumberFormatterCurrencyStyle); + currentLocale, + kCFNumberFormatterCurrencyStyle); CFNumberFormatterSetProperty(formatter, kCFNumberFormatterCurrencyCode, (__bridge CFStringRef)currencyCode); CFNumberFormatterSetProperty(formatter, kCFNumberFormatterInternationalCurrencySymbol, (__bridge CFStringRef)currencyCode); diff --git a/CordovaLib/Classes/CDVInAppBrowser.h b/CordovaLib/Classes/CDVInAppBrowser.h index 9ff460a..765326a 100755 --- a/CordovaLib/Classes/CDVInAppBrowser.h +++ b/CordovaLib/Classes/CDVInAppBrowser.h @@ -20,33 +20,29 @@ #import "CDVPlugin.h" #import "CDVInvokedUrlCommand.h" #import "CDVScreenOrientationDelegate.h" +#import "CDVWebViewDelegate.h" @class CDVInAppBrowserViewController; -@protocol CDVInAppBrowserNavigationDelegate - -- (void)browserLoadStart:(NSURL*)url; -- (void)browserLoadStop:(NSURL*)url; -- (void)browserExit; - -@end - -@interface CDVInAppBrowser : CDVPlugin +@interface CDVInAppBrowser : CDVPlugin { + BOOL _injectedIframeBridge; +} @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; @property (nonatomic, copy) NSString* callbackId; - (void)open:(CDVInvokedUrlCommand*)command; - (void)close:(CDVInvokedUrlCommand*)command; +- (void)injectScriptCode:(CDVInvokedUrlCommand*)command; @end @interface CDVInAppBrowserViewController : UIViewController { @private - NSURL* _requestedURL; NSString* _userAgent; NSString* _prevUserAgent; NSInteger _userAgentLockToken; + CDVWebViewDelegate* _webViewDelegate; } @property (nonatomic, strong) IBOutlet UIWebView* webView; @@ -58,7 +54,8 @@ @property (nonatomic, strong) IBOutlet UIToolbar* toolbar; @property (nonatomic, weak) id orientationDelegate; -@property (nonatomic, weak) id navigationDelegate; +@property (nonatomic, weak) CDVInAppBrowser* navigationDelegate; +@property (nonatomic) NSURL* currentURL; - (void)close; - (void)navigateTo:(NSURL*)url; diff --git a/CordovaLib/Classes/CDVInAppBrowser.m b/CordovaLib/Classes/CDVInAppBrowser.m index 14671a5..b03d1fe 100755 --- a/CordovaLib/Classes/CDVInAppBrowser.m +++ b/CordovaLib/Classes/CDVInAppBrowser.m @@ -20,6 +20,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVInAppBrowser.h" #import "CDVPluginResult.h" #import "CDVUserAgentUtil.h" +#import "CDVJSON.h" #define kInAppBrowserTargetSelf @"_self" #define kInAppBrowserTargetSystem @"_system" @@ -159,24 +160,162 @@ - (void)openInSystem:(NSURL*)url } } -#pragma mark CDVInAppBrowserNavigationDelegate +// This is a helper method for the inject{Script|Style}{Code|File} API calls, which +// provides a consistent method for injecting JavaScript code into the document. +// +// If a wrapper string is supplied, then the source string will be JSON-encoded (adding +// quotes) and wrapped using string formatting. (The wrapper string should have a single +// '%@' marker). +// +// If no wrapper is supplied, then the source string is executed directly. -- (void)browserLoadStart:(NSURL*)url +- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper { - if (self.callbackId != nil) { + if (!_injectedIframeBridge) { + _injectedIframeBridge = YES; + // Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e = _cdvIframeBridge = d.createElement('iframe');e.style.display='none';d.body.appendChild(e);})(document)"]; + } + + if (jsWrapper != nil) { + NSString* sourceArrayString = [@[source] JSONString]; + if (sourceArrayString) { + NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)]; + NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString]; + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject]; + } + } else { + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source]; + } +} + +- (void)injectScriptCode:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper = nil; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+window.escape(JSON.stringify([eval(%%@)]));", command.callbackId]; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectScriptFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleCode:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +/** + * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging + * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no + * other code execution is possible. + * + * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form: + * + * gap-iab:/// + * + * where is the string id of the callback to trigger (something like "InAppBrowser0123456789") + * + * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded + * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION + * is returned if the JSON is invalid. + */ +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSURL* url = request.URL; + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + + // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute, + // and the path, if present, should be a JSON-encoded value to pass to the callback. + if ([[url scheme] isEqualToString:@"gap-iab"]) { + NSString* scriptCallbackId = [url host]; + CDVPluginResult* pluginResult = nil; + + if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) { + NSString* scriptResult = [url path]; + NSError* __autoreleasing error = nil; + + // The message should be a JSON-encoded array of the result of the script which executed. + if ((scriptResult != nil) && ([scriptResult length] > 1)) { + scriptResult = [scriptResult substringFromIndex:1]; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; + } + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; + return NO; + } + } else if ((self.callbackId != nil) && isTopLevelNavigation) { + // Send a loadstart event for each top-level navigation (includes redirects). CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"loadstart", @"url":[url absoluteString]}]; + messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; } + + return YES; +} + +- (void)webViewDidStartLoad:(UIWebView*)theWebView +{ + _injectedIframeBridge = NO; } -- (void)browserLoadStop:(NSURL*)url +- (void)webViewDidFinishLoad:(UIWebView*)theWebView { if (self.callbackId != nil) { + // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected). + NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"loadstop", @"url":[url absoluteString]}]; + messageAsDictionary:@{@"type":@"loadstop", @"url":url}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } +} + +- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error +{ + if (self.callbackId != nil) { + NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR + messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInt:error.code], @"message": error.localizedDescription}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; @@ -187,7 +326,7 @@ - (void)browserExit { if (self.callbackId != nil) { CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"exit"}]; + messageAsDictionary:@{@"type":@"exit"}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; @@ -203,12 +342,15 @@ - (void)browserExit @implementation CDVInAppBrowserViewController +@synthesize currentURL; + - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent { self = [super init]; if (self != nil) { _userAgent = userAgent; _prevUserAgent = prevUserAgent; + _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self]; [self createViews]; } @@ -229,7 +371,7 @@ - (void)createViews [self.view addSubview:self.webView]; [self.view sendSubviewToBack:self.webView]; - self.webView.delegate = self; + self.webView.delegate = _webViewDelegate; self.webView.backgroundColor = [UIColor whiteColor]; self.webView.clearsContextBeforeDrawing = YES; @@ -376,6 +518,8 @@ - (void)close [[self parentViewController] dismissModalViewControllerAnimated:YES]; } + self.currentURL = nil; + if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) { [self.navigationDelegate browserExit]; } @@ -385,16 +529,14 @@ - (void)navigateTo:(NSURL*)url { NSURLRequest* request = [NSURLRequest requestWithURL:url]; - _requestedURL = url; - if (_userAgentLockToken != 0) { [self.webView loadRequest:request]; } else { [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { - _userAgentLockToken = lockToken; - [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken]; - [self.webView loadRequest:request]; - }]; + _userAgentLockToken = lockToken; + [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken]; + [self.webView loadRequest:request]; + }]; } } @@ -420,20 +562,24 @@ - (void)webViewDidStartLoad:(UIWebView*)theWebView [self.spinner startAnimating]; - if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStart:)]) { - NSURL* url = theWebView.request.URL; - if (url == nil) { - url = _requestedURL; - } - [self.navigationDelegate browserLoadStart:url]; + return [self.navigationDelegate webViewDidStartLoad:theWebView]; +} + +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + + if (isTopLevelNavigation) { + self.currentURL = request.URL; } + return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; } - (void)webViewDidFinishLoad:(UIWebView*)theWebView { // update url, stop spinner, update back/forward - self.addressLabel.text = theWebView.request.URL.absoluteString; + self.addressLabel.text = [self.currentURL absoluteString]; self.backButton.enabled = theWebView.canGoBack; self.forwardButton.enabled = theWebView.canGoForward; @@ -450,15 +596,12 @@ - (void)webViewDidFinishLoad:(UIWebView*)theWebView // from it must pass through its white-list. This *does* break PDFs that // contain links to other remote PDF/websites. // More info at https://issues.apache.org/jira/browse/CB-2225 - BOOL isPDF = [@"true" isEqualToString:[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]]; + BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]]; if (isPDF) { [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken]; } - if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStop:)]) { - NSURL* url = theWebView.request.URL; - [self.navigationDelegate browserLoadStop:url]; - } + [self.navigationDelegate webViewDidFinishLoad:theWebView]; } - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error @@ -471,6 +614,8 @@ - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error [self.spinner stopAnimating]; self.addressLabel.text = @"Load Error"; + + [self.navigationDelegate webView:theWebView didFailLoadWithError:error]; } #pragma mark CDVScreenOrientationDelegate diff --git a/CordovaLib/Classes/CDVInvokedUrlCommand.h b/CordovaLib/Classes/CDVInvokedUrlCommand.h index 6eb0099..7be8884 100755 --- a/CordovaLib/Classes/CDVInvokedUrlCommand.h +++ b/CordovaLib/Classes/CDVInvokedUrlCommand.h @@ -34,9 +34,9 @@ + (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry; - (id)initWithArguments:(NSArray*)arguments - callbackId :(NSString*)callbackId - className :(NSString*)className - methodName :(NSString*)methodName; + callbackId:(NSString*)callbackId + className:(NSString*)className + methodName:(NSString*)methodName; - (id)initFromJson:(NSArray*)jsonEntry; diff --git a/CordovaLib/Classes/CDVJpegHeaderWriter.h b/CordovaLib/Classes/CDVJpegHeaderWriter.h new file mode 100755 index 0000000..3b43ef0 --- /dev/null +++ b/CordovaLib/Classes/CDVJpegHeaderWriter.h @@ -0,0 +1,62 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface CDVJpegHeaderWriter : NSObject { + NSDictionary * SubIFDTagFormatDict; + NSDictionary * IFD0TagFormatDict; +} + +- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata + withExifBlock: (NSString*) exifstr; +- (NSString*) createExifAPP1 : (NSDictionary*) datadict; +- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb + withPlaces: (NSNumber*) width; +- (NSString*) formatNumberWithLeadingZeroes: (NSNumber*) numb + withPlaces: (NSNumber*) places; +- (NSString*) decimalToUnsignedRational: (NSNumber*) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator; +- (void) continuedFraction: (double) val + withFractionList: (NSMutableArray*) fractionlist + withHorizon: (int) horizon; +//- (void) expandContinuedFraction: (NSArray*) fractionlist; +- (void) splitDouble: (double) val + withIntComponent: (int*) rightside + withFloatRemainder: (double*) leftside; +- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator + withDenominator: (NSNumber*) denominator + asSigned: (Boolean) signedFlag; +- (NSString*) hexStringFromData : (NSData*) data; +- (NSNumber*) numericFromHexString : (NSString *) hexstring; + +/* +- (void) readExifMetaData : (NSData*) imgdata; +- (void) spliceImageData : (NSData*) imgdata withExifData: (NSDictionary*) exifdata; +- (void) locateExifMetaData : (NSData*) imgdata; +- (NSString*) createExifAPP1 : (NSDictionary*) datadict; +- (void) createExifDataString : (NSDictionary*) datadict; +- (NSString*) createDataElement : (NSString*) element + withElementData: (NSString*) data + withExternalDataBlock: (NSDictionary*) memblock; +- (NSString*) hexStringFromData : (NSData*) data; +- (NSNumber*) numericFromHexString : (NSString *) hexstring; +*/ +@end diff --git a/CordovaLib/Classes/CDVJpegHeaderWriter.m b/CordovaLib/Classes/CDVJpegHeaderWriter.m new file mode 100755 index 0000000..93cafb8 --- /dev/null +++ b/CordovaLib/Classes/CDVJpegHeaderWriter.m @@ -0,0 +1,547 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVJpegHeaderWriter.h" +#include "CDVExif.h" + +/* macros for tag info shorthand: + tagno : tag number + typecode : data type + components : number of components + appendString (TAGINF_W_APPEND only) : string to append to data + Exif date data format include an extra 0x00 to the end of the data + */ +#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil] +#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil] + +const uint mJpegId = 0xffd8; // JPEG format marker +const uint mExifMarker = 0xffe1; // APP1 jpeg header marker +const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size +const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane' +const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world' +const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number + + +@implementation CDVJpegHeaderWriter + +- (id) init { + self = [super init]; + // supported tags for exif IFD + IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys: + // TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription", + TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime", + TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make", + TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model", + TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software", + TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution", + TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution", + // currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m + /* TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation", + + // rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata + // should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully + TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit", + TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint", + TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities", + TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients", + TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning", + TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite", + TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright", + + // offset to exif subifd, we determine this dynamically based on the size of the main exif IFD + TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/ + nil]; + + + // supported tages for exif subIFD + SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys: + //TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion", + //TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue", + //TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue", + TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace", + TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized", + TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal", + TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode", + TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram", + //TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime", + //TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber", + TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash", + // FocalLengthIn35mmFilm + TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm", + //TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength", + //TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings", + TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode", + // specific to compressed data + TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension", + TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension", + // data type undefined, but this is a DSC camera, so value is always 1, treat as ushort + TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType", + TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod", + //TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue", + // specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing + //TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea", + TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance", + nil]; + return self; +} + +- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr { + + CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init]; + + NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2]; + int idx; + for (idx = 0; idx+1 < [exifstr length]; idx+=2) { + NSRange range = NSMakeRange(idx, 2); + NSString* hexStr = [exifstr substringWithRange:range]; + NSScanner* scanner = [NSScanner scannerWithString:hexStr]; + unsigned int intValue; + [scanner scanHexInt:&intValue]; + [exifdata appendBytes:&intValue length:1]; + } + + NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]]; + NSMakeRange(0,4); + int loc = 0; + bool done = false; + // read the jpeg data until we encounter the app1==0xFFE1 marker + while (loc+1 < [jpegdata length]) { + NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)]; + if( [[blag description] isEqualToString : @""]) { + // read the APP1 block size bits + NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]]; + NSNumber * app1width = [exifWriter numericFromHexString:the]; + //consume the original app1 block + [ddata appendData:exifdata]; + // advance our loc marker past app1 + loc += [app1width intValue] + 2; + done = true; + } else { + if(!done) { + [ddata appendData:blag]; + loc += 2; + } else { + break; + } + } + } + // copy the remaining data + [ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]]; + return ddata; +} + + + +/** + * Create the Exif data block as a hex string + * jpeg uses Application Markers (APP's) as markers for application data + * APP1 is the application marker reserved for exif data + * + * (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid + * didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata" + * + * the following constructs a hex string to Exif specifications, and is therefore brittle + * altering the order of arguments to the string constructors, modifying field sizes or formats, + * and any other minor change will likely prevent the exif data from being read + */ +- (NSString*) createExifAPP1 : (NSDictionary*) datadict { + NSMutableString * app1; // holds finalized product + NSString * exifIFD; // exif information file directory + NSString * subExifIFD; // subexif information file directory + + // FFE1 is the hex APP1 marker code, and will allow client apps to read the data + NSString * app1marker = @"ffe1"; + // SSSS size, to be determined + // EXIF ascii characters followed by 2bytes of zeros + NSString * exifmarker = @"457869660000"; + // Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42 + NSString * tiffheader = @"4d4d002a"; + //first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08 + NSString * ifd0offset = @"00000008"; + // current offset to next data area + int currentDataOffset = 0; + + //data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1 + exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:¤tDataOffset]; + + //data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1 + subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:¤tDataOffset]; + /* + NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",exifIFD,[exifIFD length]); + + NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",subExifIFD,[subExifIFD length]); + */ + // construct the complete app1 data block + app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@", + app1marker, + 16 + ([exifIFD length]/2) + ([subExifIFD length]/2) /*16+[exifIFD length]/2*/, + exifmarker, + tiffheader, + ifd0offset, + exifIFD, + subExifIFD]; + + return app1; +} + +// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict +- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict + withFormatDict : (NSDictionary*) formatdict + isIFD0 : (BOOL) ifd0flag + currentDataOffset : (int*) dataoffset { + NSArray * datakeys = [datadict allKeys]; // all known data keys + NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD + NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries + NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries + // ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end + + // iterate through known provided data keys + for (int i = 0; i < [datakeys count]; i++) { + NSString * key = [datakeys objectAtIndex:i]; + // don't muck about with unknown keys + if ([knownkeys indexOfObject: key] != NSNotFound) { + // create new IFD entry + NSString * entry = [self createIFDElement: key + withFormat: [formatdict objectForKey:key] + withElementData: [datadict objectForKey:key]]; + // create the IFD entry's data block + NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key] + withData: [datadict objectForKey:key]]; + if (entry) { + [ifdblock addObject:entry]; + if(!data) { + [ifdblock addObject:@""]; + } else { + [ifddatablock addObject:data]; + } + } + } + } + + NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24]; + NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100]; + + int addr=*dataoffset; // current offset/address in datablock + if (ifd0flag) { + // calculate offset to datablock based on ifd file entry count + addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset + } else { + // current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes) + addr += 2+(12*[ifddatablock count])+4; + } + + for (int i = 0; i < [ifdblock count]; i++) { + NSString * entry = [ifdblock objectAtIndex:i]; + NSString * data = [ifddatablock objectAtIndex:i]; + + // check if the data fits into 4 bytes + if( [data length] <= 8) { + // concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string + [exifstr appendFormat : @"%@%@", entry, data]; + } else { + [exifstr appendFormat : @"%@%08x", entry, addr]; + [dbstr appendFormat: @"%@", data]; + addr+= [data length] / 2; + /* + NSLog(@"=====data-length[%i]=======",[data length]); + NSLog(@"addr-offset[%i]",addr); + NSLog(@"entry[%@]",entry); + NSLog(@"data[%@]",data); + */ + } + } + + // calculate IFD0 terminal offset tags, currently ExifSubIFD + int entrycount = [ifdblock count]; + if (ifd0flag) { + // 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header + NSNumber * offset = [NSNumber numberWithInt:[exifstr length] / 2 + [dbstr length] / 2 + 18+8]; + + [self appendExifOffsetTagTo: exifstr + withOffset : offset]; + entrycount++; + } + *dataoffset = addr; + return [[NSString alloc] initWithFormat: @"%04x%@%@%@", + entrycount, + exifstr, + @"00000000", // offset to next IFD, 0 since there is none + dbstr]; // lastly, the datablock +} + +// Creates an exif formatted exif information file directory entry +- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data { + //NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field + if (formtemplate) { + // format string @"%@%@%@%@", tag number, data format, components, value + NSNumber * dataformat = [formtemplate objectAtIndex:1]; + NSNumber * components = [formtemplate objectAtIndex:2]; + if([components intValue] == 0) { + components = [NSNumber numberWithInt: [data length] * DataTypeToWidth[[dataformat intValue]-1]]; + } + + return [[NSString alloc] initWithFormat: @"%@%@%08x", + [formtemplate objectAtIndex:0], // the field code + [self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code + [components intValue]]; // number of components + } + return NULL; +} + +/** + * appends exif IFD0 tag 8769 "ExifOffset" to the string provided + * (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string + * // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset", + */ +- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset { + NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1); + + NSString * entry = [self createIFDElement: @"ExifOffset" + withFormat: format + withElementData: [offset stringValue]]; + + NSString * data = [self createIFDElementDataWithFormat: format + withData: [offset stringValue]]; + [str appendFormat:@"%@%@", entry, data]; +} + +// formats the Information File Directory Data to exif format +- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data { + NSMutableString * datastr = nil; + NSNumber * tmp = nil; + NSNumber * formatcode = [dataformat objectAtIndex:1]; + NSUInteger formatItemsCount = [dataformat count]; + NSNumber * num = @0; + NSNumber * denom = @0; + + switch ([formatcode intValue]) { + case EDT_UBYTE: + break; + case EDT_ASCII_STRING: + datastr = [[NSMutableString alloc] init]; + for (int i = 0; i < [data length]; i++) { + [datastr appendFormat:@"%02x",[data characterAtIndex:i]]; + } + if (formatItemsCount > 3) { + // We have additional data to append. + // currently used by Date format to append final 0x00 but can be used by other data types as well in the future + [datastr appendString:[dataformat objectAtIndex:3]]; + } + if ([datastr length] < 8) { + NSString * format = [NSString stringWithFormat:@"%%0%dd", 8 - [datastr length]]; + [datastr appendFormat:format,0]; + } + return datastr; + case EDT_USHORT: + return [[NSString alloc] initWithFormat : @"%@%@", + [self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4], + @"0000"]; + case EDT_ULONG: + tmp = [NSNumber numberWithUnsignedLong:[data intValue]]; + return [NSString stringWithFormat : @"%@", + [self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]]; + case EDT_URATIONAL: + return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]] + withResultNumerator: &num + withResultDenominator: &denom]; + case EDT_SBYTE: + + break; + case EDT_UNDEFINED: + break; // 8 bits + case EDT_SSHORT: + break; + case EDT_SLONG: + break; // 32bit signed integer (2's complement) + case EDT_SRATIONAL: + break; // 2 SLONGS, first long is numerator, second is denominator + case EDT_SINGLEFLOAT: + break; + case EDT_DOUBLEFLOAT: + break; + } + return datastr; +} + +//====================================================================================================================== +// Utility Methods +//====================================================================================================================== + +// creates a formatted little endian hex string from a number and width specifier +- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]]; + NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]]; + [str appendFormat:formatstr, [numb intValue]]; + return str; +} + +// format number as string with leading 0's +- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places { + NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init]; + NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0]; + [formatter setPositiveFormat:formatstr]; + return [formatter stringFromNumber:numb]; +} + +// approximate a decimal with a rational by method of continued fraction +// can be collasped into decimalToUnsignedRational after testing +- (void) decimalToRational: (NSNumber *) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8]; + + [self continuedFraction: [numb doubleValue] + withFractionList: fractionlist + withHorizon: 8]; + + // simplify complex fraction represented by partial fraction list + [self expandContinuedFraction: fractionlist + withResultNumerator: numerator + withResultDenominator: denominator]; + +} + +// approximate a decimal with an unsigned rational by method of continued fraction +- (NSString*) decimalToUnsignedRational: (NSNumber *) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8]; + + // generate partial fraction list + [self continuedFraction: [numb doubleValue] + withFractionList: fractionlist + withHorizon: 8]; + + // simplify complex fraction represented by partial fraction list + [self expandContinuedFraction: fractionlist + withResultNumerator: numerator + withResultDenominator: denominator]; + + return [self formatFractionList: fractionlist]; +} + +// recursive implementation of decimal approximation by continued fraction +- (void) continuedFraction: (double) val + withFractionList: (NSMutableArray*) fractionlist + withHorizon: (int) horizon { + int whole; + double remainder; + // 1. split term + [self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder]; + [fractionlist addObject: [NSNumber numberWithInt:whole]]; + + // 2. calculate reciprocal of remainder + if (!remainder) return; // early exit, exact fraction found, avoids recip/0 + double recip = 1 / remainder; + + // 3. exit condition + if ([fractionlist count] > horizon) { + return; + } + + // 4. recurse + [self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon]; + +} + +// expand continued fraction list, creating a single level rational approximation +-(void) expandContinuedFraction: (NSArray*) fractionlist + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + int i = 0; + int den = 0; + int num = 0; + if ([fractionlist count] == 1) { + *numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]]; + *denominator = @1; + return; + } + + //begin at the end of the list + i = [fractionlist count] - 1; + num = 1; + den = [[fractionlist objectAtIndex:i] intValue]; + + while (i > 0) { + int t = [[fractionlist objectAtIndex: i-1] intValue]; + num = t * den + num; + if (i==1) { + break; + } else { + t = num; + num = den; + den = t; + } + i--; + } + // set result parameters values + *numerator = [NSNumber numberWithInt: num]; + *denominator = [NSNumber numberWithInt: den]; +} + +// formats expanded fraction list to string matching exif specification +- (NSString*) formatFractionList: (NSArray *) fractionlist { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16]; + + if ([fractionlist count] == 1){ + [str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]]; + } + return str; +} + +// format rational as +- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16]; + if (signedFlag) { + long num = [numerator longValue]; + long den = [denominator longValue]; + [str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1]; + } else { + [str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]]; + } + return str; +} + +// split a floating point number into two integer values representing the left and right side of the decimal +- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside { + *rightside = val; // convert numb to int representation, which truncates the decimal portion + *leftside = val - *rightside; +} + + +// +- (NSString*) hexStringFromData : (NSData*) data { + //overflow detection + const unsigned char *dataBuffer = [data bytes]; + return [[NSString alloc] initWithFormat: @"%02x%02x", + (unsigned char)dataBuffer[0], + (unsigned char)dataBuffer[1]]; +} + +// convert a hex string to a number +- (NSNumber*) numericFromHexString : (NSString *) hexstring { + NSScanner * scan = NULL; + unsigned int numbuf= 0; + + scan = [NSScanner scannerWithString:hexstring]; + [scan scanHexInt:&numbuf]; + return [NSNumber numberWithInt:numbuf]; +} + +@end diff --git a/CordovaLib/Classes/CDVLocalStorage.h b/CordovaLib/Classes/CDVLocalStorage.h index cc6613f..dec6ab3 100755 --- a/CordovaLib/Classes/CDVLocalStorage.h +++ b/CordovaLib/Classes/CDVLocalStorage.h @@ -34,8 +34,8 @@ + (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType; // Visible for testing. + (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict - bundlePath :(NSString*)bundlePath - fileManager :(NSFileManager*)fileManager; + bundlePath:(NSString*)bundlePath + fileManager:(NSFileManager*)fileManager; @end @interface CDVBackupInfo : NSObject diff --git a/CordovaLib/Classes/CDVLocalStorage.m b/CordovaLib/Classes/CDVLocalStorage.m index 68175f1..238d680 100755 --- a/CordovaLib/Classes/CDVLocalStorage.m +++ b/CordovaLib/Classes/CDVLocalStorage.m @@ -35,7 +35,7 @@ - (void)pluginInitialize { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) name:UIApplicationWillResignActiveNotification object:nil]; - BOOL cloudBackup = [@"cloud" isEqualToString:self.commandDelegate.settings[@"BackupWebStorage"]]; + BOOL cloudBackup = [@"cloud" isEqualToString : self.commandDelegate.settings[@"BackupWebStorage"]]; self.backupInfo = [[self class] createBackupInfoWithCloudBackup:cloudBackup]; } @@ -64,8 +64,8 @@ + (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir: // ////////// LOCALSTORAGE original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0.localstorage":@"file__0.localstorage"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db":@"file__0.localstorage")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db" : @"file__0.localstorage")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -77,8 +77,8 @@ + (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir: // ////////// WEBSQL MAIN DB original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/Databases.db":@"Databases.db"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db":@"Databases.db")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db" : @"Databases.db")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -90,8 +90,8 @@ + (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir: // ////////// WEBSQL DATABASES original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0":@"file__0"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db":@"file__0")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db" : @"file__0")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -106,8 +106,8 @@ + (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir: + (NSMutableArray*)createBackupInfoWithCloudBackup:(BOOL)cloudBackup { // create backup info from backup folder to caches folder - NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"]; NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"]; @@ -131,7 +131,7 @@ + (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL skip:(BOOL)skip return success; } -+ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError * __autoreleasing*)error ++ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError* __autoreleasing*)error { NSFileManager* fileManager = [NSFileManager defaultManager]; @@ -149,7 +149,7 @@ + (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError * __autoreleas // generate unique filepath in temp directory CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); - NSString* tempBackup = [[NSTemporaryDirectory () stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"]; + NSString* tempBackup = [[NSTemporaryDirectory() stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"]; CFRelease(uuidString); CFRelease(uuidRef); @@ -334,8 +334,8 @@ + (void)__restoreLegacyDatabaseLocationsWithBackupType:(NSString*)backupType return; } - NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:0]; @@ -386,15 +386,15 @@ - (void)onResignActive backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; backgroundTaskID = UIBackgroundTaskInvalid; - NSLog (@"Background task to backup WebSQL/LocalStorage expired."); + NSLog(@"Background task to backup WebSQL/LocalStorage expired."); }]; CDVLocalStorage __weak* weakSelf = self; [self.commandDelegate runInBackground:^{ - [weakSelf backup:nil]; + [weakSelf backup:nil]; - [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; - backgroundTaskID = UIBackgroundTaskInvalid; - }]; + [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; + backgroundTaskID = UIBackgroundTaskInvalid; + }]; } } diff --git a/CordovaLib/Classes/CDVLocation.h b/CordovaLib/Classes/CDVLocation.h index 7087d43..caf0798 100755 --- a/CordovaLib/Classes/CDVLocation.h +++ b/CordovaLib/Classes/CDVLocation.h @@ -83,11 +83,11 @@ typedef NSUInteger CDVLocationStatus; - (void)startLocation:(BOOL)enableHighAccuracy; - (void)locationManager:(CLLocationManager*)manager - didUpdateToLocation :(CLLocation*)newLocation - fromLocation :(CLLocation*)oldLocation; + didUpdateToLocation:(CLLocation*)newLocation + fromLocation:(CLLocation*)oldLocation; - (void)locationManager:(CLLocationManager*)manager - didFailWithError :(NSError*)error; + didFailWithError:(NSError*)error; - (BOOL)isLocationServicesEnabled; @@ -97,7 +97,7 @@ typedef NSUInteger CDVLocationStatus; - (void)stopHeading:(CDVInvokedUrlCommand*)command; - (void)startHeadingWithFilter:(CLLocationDegrees)filter; - (void)locationManager:(CLLocationManager*)manager - didUpdateHeading :(CLHeading*)heading; + didUpdateHeading:(CLHeading*)heading; - (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager*)manager; diff --git a/CordovaLib/Classes/CDVLocation.m b/CordovaLib/Classes/CDVLocation.m index 07af30e..ed9ec26 100755 --- a/CordovaLib/Classes/CDVLocation.m +++ b/CordovaLib/Classes/CDVLocation.m @@ -591,17 +591,17 @@ @implementation CLLocation (JSONMethods) - (NSString*)JSONRepresentation { return [NSString stringWithFormat: - @"{ timestamp: %.00f, \ + @"{ timestamp: %.00f, \ coords: { latitude: %f, longitude: %f, altitude: %.02f, heading: %.02f, speed: %.02f, accuracy: %.02f, altitudeAccuracy: %.02f } \ }", - [self.timestamp timeIntervalSince1970] * 1000.0, - self.coordinate.latitude, - self.coordinate.longitude, - self.altitude, - self.course, - self.speed, - self.horizontalAccuracy, - self.verticalAccuracy + [self.timestamp timeIntervalSince1970] * 1000.0, + self.coordinate.latitude, + self.coordinate.longitude, + self.altitude, + self.course, + self.speed, + self.horizontalAccuracy, + self.verticalAccuracy ]; } @@ -614,9 +614,9 @@ @implementation NSError (JSONMethods) - (NSString*)JSONRepresentation { return [NSString stringWithFormat: - @"{ code: %d, message: '%@'}", - self.code, - [self localizedDescription] + @"{ code: %d, message: '%@'}", + self.code, + [self localizedDescription] ]; } diff --git a/CordovaLib/Classes/CDVNotification.h b/CordovaLib/Classes/CDVNotification.h index 1eedb54..5b5b89f 100755 --- a/CordovaLib/Classes/CDVNotification.h +++ b/CordovaLib/Classes/CDVNotification.h @@ -26,6 +26,7 @@ - (void)alert:(CDVInvokedUrlCommand*)command; - (void)confirm:(CDVInvokedUrlCommand*)command; +- (void)prompt:(CDVInvokedUrlCommand*)command; - (void)vibrate:(CDVInvokedUrlCommand*)command; @end diff --git a/CordovaLib/Classes/CDVNotification.m b/CordovaLib/Classes/CDVNotification.m index 992239e..821cb9f 100755 --- a/CordovaLib/Classes/CDVNotification.m +++ b/CordovaLib/Classes/CDVNotification.m @@ -20,12 +20,24 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVNotification.h" #import "NSDictionary+Extensions.h" +#define DIALOG_TYPE_ALERT @"alert" +#define DIALOG_TYPE_PROMPT @"prompt" + @implementation CDVNotification -- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSString*)buttons callbackId:(NSString*)callbackId +/* + * showDialogWithMessage - Common method to instantiate the alert view for alert, confirm, and prompt notifications. + * Parameters: + * message The alert view message. + * title The alert view title. + * buttons The array of customized strings for the buttons. + * callbackId The commmand callback id. + * dialogType The type of alert view [alert | prompt]. + */ +- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSArray*)buttons callbackId:(NSString*)callbackId dialogType:(NSString*)dialogType { CDVAlertView* alertView = [[CDVAlertView alloc] - initWithTitle:title + initWithTitle:title message:message delegate:self cancelButtonTitle:nil @@ -33,11 +45,14 @@ - (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons: alertView.callbackId = callbackId; - NSArray* labels = [buttons componentsSeparatedByString:@","]; - int count = [labels count]; + int count = [buttons count]; for (int n = 0; n < count; n++) { - [alertView addButtonWithTitle:[labels objectAtIndex:n]]; + [alertView addButtonWithTitle:[buttons objectAtIndex:n]]; + } + + if ([dialogType isEqualToString:DIALOG_TYPE_PROMPT]) { + alertView.alertViewStyle = UIAlertViewStylePlainTextInput; } [alertView show]; @@ -46,52 +61,54 @@ - (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons: - (void)alert:(CDVInvokedUrlCommand*)command { NSString* callbackId = command.callbackId; - NSArray* arguments = command.arguments; - int argc = [arguments count]; - - NSString* message = argc > 0 ? [arguments objectAtIndex:0] : nil; - NSString* title = argc > 1 ? [arguments objectAtIndex:1] : nil; - NSString* buttons = argc > 2 ? [arguments objectAtIndex:2] : nil; - - if (!title) { - title = NSLocalizedString(@"Alert", @"Alert"); - } - if (!buttons) { - buttons = NSLocalizedString(@"OK", @"OK"); - } + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSString* buttons = [command argumentAtIndex:2]; - [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; + [self showDialogWithMessage:message title:title buttons:@[buttons] callbackId:callbackId dialogType:DIALOG_TYPE_ALERT]; } - (void)confirm:(CDVInvokedUrlCommand*)command { NSString* callbackId = command.callbackId; - NSArray* arguments = command.arguments; - int argc = [arguments count]; + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSArray* buttons = [command argumentAtIndex:2]; - NSString* message = argc > 0 ? [arguments objectAtIndex:0] : nil; - NSString* title = argc > 1 ? [arguments objectAtIndex:1] : nil; - NSString* buttons = argc > 2 ? [arguments objectAtIndex:2] : nil; + [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId dialogType:DIALOG_TYPE_ALERT]; +} - if (!title) { - title = NSLocalizedString(@"Confirm", @"Confirm"); - } - if (!buttons) { - buttons = NSLocalizedString(@"OK,Cancel", @"OK,Cancel"); - } +- (void)prompt:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSArray* buttons = [command argumentAtIndex:2]; - [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; + [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId dialogType:DIALOG_TYPE_PROMPT]; } /** - Callback invoked when an alert dialog's buttons are clicked. - Passes the index + label back to JS - */ + * Callback invoked when an alert dialog's buttons are clicked. + */ - (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { CDVAlertView* cdvAlertView = (CDVAlertView*)alertView; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:++buttonIndex]; - + CDVPluginResult* result; + + // Determine what gets returned to JS based on the alert view type. + if (alertView.alertViewStyle == UIAlertViewStyleDefault) { + // For alert and confirm, return button index as int back to JS. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:buttonIndex + 1]; + } else { + // For prompt, return button index and input text back to JS. + NSString* value0 = [[alertView textFieldAtIndex:0] text]; + NSDictionary* info = @{ + @"buttonIndex":@(buttonIndex + 1), + @"input1":(value0 ? value0 : [NSNull null]) + }; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:info]; + } [self.commandDelegate sendPluginResult:result callbackId:cdvAlertView.callbackId]; } diff --git a/CordovaLib/Classes/CDVPlugin.h b/CordovaLib/Classes/CDVPlugin.h index f5b50eb..33ba1c4 100755 --- a/CordovaLib/Classes/CDVPlugin.h +++ b/CordovaLib/Classes/CDVPlugin.h @@ -23,10 +23,10 @@ #import "NSMutableArray+QueueAdditions.h" #import "CDVCommandDelegate.h" -#define CDVPageDidLoadNotification @"CDVPageDidLoadNotification" -#define CDVPluginHandleOpenURLNotification @"CDVPluginHandleOpenURLNotification" -#define CDVPluginResetNotification @"CDVPluginResetNotification" -#define CDVLocalNotification @"CDVLocalNotification" +extern NSString* const CDVPageDidLoadNotification; +extern NSString* const CDVPluginHandleOpenURLNotification; +extern NSString* const CDVPluginResetNotification; +extern NSString* const CDVLocalNotification; @interface CDVPlugin : NSObject {} diff --git a/CordovaLib/Classes/CDVPlugin.m b/CordovaLib/Classes/CDVPlugin.m index a42d241..8c932a0 100755 --- a/CordovaLib/Classes/CDVPlugin.m +++ b/CordovaLib/Classes/CDVPlugin.m @@ -19,6 +19,11 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVPlugin.h" +NSString* const CDVPageDidLoadNotification = @"CDVPageDidLoadNotification"; +NSString* const CDVPluginHandleOpenURLNotification = @"CDVPluginHandleOpenURLNotification"; +NSString* const CDVPluginResetNotification = @"CDVPluginResetNotification"; +NSString* const CDVLocalNotification = @"CDVLocalNotification"; + @interface CDVPlugin () @property (readwrite, assign) BOOL hasPendingOperation; @@ -41,7 +46,7 @@ - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURL:) name:CDVPluginHandleOpenURLNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:theWebView]; self.webView = theWebView; } @@ -64,7 +69,7 @@ - (void)pluginInitialize // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveLocalNotification:) name:CDVLocalNotification object:nil]; // Added in 2.5.0 - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:self.webView]; } - (void)dispose diff --git a/CordovaLib/Classes/CDVPluginResult.h b/CordovaLib/Classes/CDVPluginResult.h index 8683205..11b5377 100755 --- a/CordovaLib/Classes/CDVPluginResult.h +++ b/CordovaLib/Classes/CDVPluginResult.h @@ -37,6 +37,9 @@ typedef enum { @property (nonatomic, strong, readonly) NSNumber* status; @property (nonatomic, strong, readonly) id message; @property (nonatomic, strong) NSNumber* keepCallback; +// This property can be used to scope the lifetime of another object. For example, +// Use it to store the associated NSData when `message` is created using initWithBytesNoCopy. +@property (nonatomic, strong) id associatedObject; - (CDVPluginResult*)init; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal; @@ -47,6 +50,7 @@ typedef enum { + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode; + (void)setVerbose:(BOOL)verbose; @@ -54,6 +58,9 @@ typedef enum { - (void)setKeepCallbackAsBool:(BOOL)bKeepCallback; +- (NSString*)argumentsAsJSON; + +// These methods are used by the legacy plugin return result method - (NSString*)toJSONString; - (NSString*)toSuccessCallbackString:(NSString*)callbackId; - (NSString*)toErrorCallbackString:(NSString*)callbackId; diff --git a/CordovaLib/Classes/CDVPluginResult.m b/CordovaLib/Classes/CDVPluginResult.m index d9ba08f..af7c528 100755 --- a/CordovaLib/Classes/CDVPluginResult.m +++ b/CordovaLib/Classes/CDVPluginResult.m @@ -29,10 +29,40 @@ - (CDVPluginResult*)initWithStatus:(CDVCommandStatus)statusOrdinal message:(id)t @end @implementation CDVPluginResult -@synthesize status, message, keepCallback; +@synthesize status, message, keepCallback, associatedObject; static NSArray* org_apache_cordova_CommandStatusMsgs; +id messageFromArrayBuffer(NSData* data) +{ + return @{ + @"CDVType" : @"ArrayBuffer", + @"data" :[data base64EncodedString] + }; +} + +id massageMessage(id message) +{ + if ([message isKindOfClass:[NSData class]]) { + return messageFromArrayBuffer(message); + } + return message; +} + +id messageFromMultipart(NSArray* theMessages) +{ + NSMutableArray* messages = [NSMutableArray arrayWithArray:theMessages]; + + for (NSUInteger i = 0; i < messages.count; ++i) { + [messages replaceObjectAtIndex:i withObject:massageMessage([messages objectAtIndex:i])]; + } + + return @{ + @"CDVType" : @"MultiPart", + @"messages" : messages + }; +} + + (void)initialize { org_apache_cordova_CommandStatusMsgs = [[NSArray alloc] initWithObjects:@"No result", @@ -101,17 +131,17 @@ + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDi + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage { - NSDictionary* arrDict = [NSDictionary dictionaryWithObjectsAndKeys: - @"ArrayBuffer", @"CDVType", - [theMessage base64EncodedString], @"data", - nil]; + return [[self alloc] initWithStatus:statusOrdinal message:messageFromArrayBuffer(theMessage)]; +} - return [[self alloc] initWithStatus:statusOrdinal message:arrDict]; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages +{ + return [[self alloc] initWithStatus:statusOrdinal message:messageFromMultipart(theMessages)]; } + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode { - NSDictionary* errDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"code"]; + NSDictionary* errDict = @{@"code" :[NSNumber numberWithInt:errorCode]}; return [[self alloc] initWithStatus:statusOrdinal message:errDict]; } @@ -121,6 +151,19 @@ - (void)setKeepCallbackAsBool:(BOOL)bKeepCallback [self setKeepCallback:[NSNumber numberWithBool:bKeepCallback]]; } +- (NSString*)argumentsAsJSON +{ + id arguments = (self.message == nil ? [NSNull null] : self.message); + NSArray* argumentsWrappedInArray = [NSArray arrayWithObject:arguments]; + + NSString* argumentsJSON = [argumentsWrappedInArray JSONString]; + + argumentsJSON = [argumentsJSON substringWithRange:NSMakeRange(1, [argumentsJSON length] - 2)]; + + return argumentsJSON; +} + +// These methods are used by the legacy plugin return result method - (NSString*)toJSONString { NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: diff --git a/CordovaLib/Classes/CDVReachability.m b/CordovaLib/Classes/CDVReachability.m index 3c5a48b..89f4ec9 100755 --- a/CordovaLib/Classes/CDVReachability.m +++ b/CordovaLib/Classes/CDVReachability.m @@ -61,18 +61,18 @@ static void CDVPrintReachabilityFlags(SCNetworkReachabilityFlags flags, const ch { #if kShouldPrintReachabilityFlags NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", - (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', - (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', - - (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', - (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', - (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', - comment - ); + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); #endif } @@ -90,7 +90,7 @@ static void CDVReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRe return; } - if (![(__bridge NSObject*) info isKindOfClass:[CDVReachability class]]) { + if (![(__bridge NSObject*)info isKindOfClass :[CDVReachability class]]) { NSLog(@"info was wrong class in ReachabilityCallback"); return; } @@ -214,7 +214,7 @@ - (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags } if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || - ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))) { + ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))) { // ... and the connection is on-demand (or on-traffic) if the // calling application is using the CFSocketStream or higher APIs diff --git a/CordovaLib/Classes/CDVSound.m b/CordovaLib/Classes/CDVSound.m index 99515d7..71eab59 100755 --- a/CordovaLib/Classes/CDVSound.m +++ b/CordovaLib/Classes/CDVSound.m @@ -87,7 +87,7 @@ - (NSURL*)urlForRecording:(NSString*)resourcePath NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); } else { // if resourcePath is not from FileSystem put in tmp dir, else attempt to use provided resource path - NSString* tmpPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* tmpPath = [NSTemporaryDirectory()stringByStandardizingPath]; BOOL isTmp = [resourcePath rangeOfString:tmpPath].location != NSNotFound; BOOL isDoc = [resourcePath rangeOfString:docsPath].location != NSNotFound; if (!isTmp && !isDoc) { @@ -127,7 +127,7 @@ - (NSURL*)urlForPlaying:(NSString*)resourcePath filePath = [self.commandDelegate pathForResource:resourcePath]; if (filePath == nil) { // see if this exists in the documents/temp directory from a previous recording - NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory ()stringByStandardizingPath], resourcePath]; + NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], resourcePath]; if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) { // inefficient as existence will be checked again below but only way to determine if file exists from previous recording filePath = testPath; @@ -273,6 +273,9 @@ - (void)setVolume:(CDVInvokedUrlCommand*)command } else { audioFile = [[self soundCache] objectForKey:mediaId]; audioFile.volume = volume; + if (audioFile.player) { + audioFile.player.volume = [volume floatValue]; + } [[self soundCache] setObject:audioFile forKey:mediaId]; } @@ -385,7 +388,7 @@ - (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId // bug in AVAudioPlayer when playing downloaded data in NSData - we have to download the file and play from disk CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); - NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory ()stringByStandardizingPath], uuidString]; + NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], uuidString]; CFRelease(uuidString); CFRelease(uuidRef); @@ -458,9 +461,19 @@ - (void)seekToAudio:(CDVInvokedUrlCommand*)command double position = [[command.arguments objectAtIndex:1] doubleValue]; if ((audioFile != nil) && (audioFile.player != nil)) { + NSString* jsString; double posInSeconds = position / 1000; - audioFile.player.currentTime = posInSeconds; - NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + if (posInSeconds >= audioFile.player.duration) { + // The seek is past the end of file. Stop media and reset to beginning instead of seeking past the end. + [audioFile.player stop]; + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, 0.0, @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + // NSLog(@"seekToEndJsString=%@",jsString); + } else { + audioFile.player.currentTime = posInSeconds; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + // NSLog(@"seekJsString=%@",jsString); + } [self.commandDelegate evalJs:jsString]; } @@ -625,6 +638,7 @@ - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)fl NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath); } if (flag) { + audioFile.player.currentTime = 0; jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; } else { // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE]; diff --git a/CordovaLib/Classes/CDVSplashScreen.h b/CordovaLib/Classes/CDVSplashScreen.h index a0868a0..704ab43 100755 --- a/CordovaLib/Classes/CDVSplashScreen.h +++ b/CordovaLib/Classes/CDVSplashScreen.h @@ -23,7 +23,8 @@ @interface CDVSplashScreen : CDVPlugin { UIActivityIndicatorView* _activityView; UIImageView* _imageView; - UIView* _parentView; + NSString* _curImageName; + BOOL _visible; } - (void)show:(CDVInvokedUrlCommand*)command; diff --git a/CordovaLib/Classes/CDVSplashScreen.m b/CordovaLib/Classes/CDVSplashScreen.m index cba1b53..45889a0 100755 --- a/CordovaLib/Classes/CDVSplashScreen.m +++ b/CordovaLib/Classes/CDVSplashScreen.m @@ -19,29 +19,25 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVSplashScreen.h" -#define kSplashScreenStateShow 0 -#define kSplashScreenStateHide 1 - #define kSplashScreenDurationDefault 0.25f @implementation CDVSplashScreen - (void)pluginInitialize { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView]; - [self show:nil]; + [self setVisible:YES]; } - (void)show:(CDVInvokedUrlCommand*)command { - [self updateSplashScreenWithState:kSplashScreenStateShow]; + [self setVisible:YES]; } - (void)hide:(CDVInvokedUrlCommand*)command { - [self updateSplashScreenWithState:kSplashScreenStateHide]; + [self setVisible:NO]; } - (void)pageDidLoad @@ -50,16 +46,13 @@ - (void)pageDidLoad // if value is missing, default to yes if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { - [self hide:nil]; + [self setVisible:NO]; } } -- (void)onOrientationWillChange:(NSNotification*)notification +- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { - if (_imageView != nil) { - UIInterfaceOrientation orientation = [notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] intValue]; - [self updateSplashImageForOrientation:orientation]; - } + [self updateImage]; } - (void)createViews @@ -83,43 +76,120 @@ - (void)createViews topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; } + UIView* parentView = self.viewController.view; + parentView.userInteractionEnabled = NO; // disable user interaction while splashscreen is shown _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; - _activityView.tag = 2; - _activityView.center = self.viewController.view.center; + _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2); + _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin + | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; [_activityView startAnimating]; + // Set the frame & image later. _imageView = [[UIImageView alloc] init]; - [self.viewController.view addSubview:_imageView]; - [self.viewController.view.superview addSubview:_activityView]; - [self.viewController.view.superview layoutSubviews]; + [parentView addSubview:_imageView]; + + id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:@"ShowSplashScreenSpinner"]; + // backwards compatibility - if key is missing, default to true + if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { + [parentView addSubview:_activityView]; + } + + // Frame is required when launching in portrait mode. + // Bounds for landscape since it captures the rotation. + [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil]; + [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil]; + + [self updateImage]; +} + +- (void)destroyViews +{ + [_imageView removeFromSuperview]; + [_activityView removeFromSuperview]; + _imageView = nil; + _activityView = nil; + _curImageName = nil; + + self.viewController.view.userInteractionEnabled = YES; // re-enable user interaction upon completion + [self.viewController.view removeObserver:self forKeyPath:@"frame"]; + [self.viewController.view removeObserver:self forKeyPath:@"bounds"]; } -- (void)updateSplashImageForOrientation:(UIInterfaceOrientation)orientation +// Sets the view's frame and image. +- (void)updateImage { - // IPHONE (default) - NSString* imageName = @"Default"; + UIInterfaceOrientation orientation = self.viewController.interfaceOrientation; + + // Use UILaunchImageFile if specified in plist. Otherwise, use Default. + NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; + + if (imageName) { + imageName = [imageName stringByDeletingPathExtension]; + } else { + imageName = @"Default"; + } if (CDV_IsIPhone5()) { imageName = [imageName stringByAppendingString:@"-568h"]; } else if (CDV_IsIPad()) { - // set default to portrait upside down - imageName = @"Default-Portrait"; // @"Default-PortraitUpsideDown.png"; + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + case UIInterfaceOrientationLandscapeRight: + imageName = [imageName stringByAppendingString:@"-Landscape"]; + break; + + case UIInterfaceOrientationPortrait: + case UIInterfaceOrientationPortraitUpsideDown: + default: + imageName = [imageName stringByAppendingString:@"-Portrait"]; + break; + } + } + + if (![imageName isEqualToString:_curImageName]) { + UIImage* img = [UIImage imageNamed:imageName]; + _imageView.image = img; + _curImageName = imageName; + } - if (orientation == UIInterfaceOrientationLandscapeLeft) { - imageName = @"Default-Landscape.png"; // @"Default-LandscapeLeft.png"; - } else if (orientation == UIInterfaceOrientationLandscapeRight) { - imageName = @"Default-Landscape.png"; // @"Default-LandscapeRight.png"; + [self updateBounds]; +} + +- (void)updateBounds +{ + UIImage* img = _imageView.image; + CGRect imgBounds = CGRectMake(0, 0, img.size.width, img.size.height); + + CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size; + + // There's a special case when the image is the size of the screen. + if (CGSizeEqualToSize(screenSize, imgBounds.size)) { + CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil]; + imgBounds.origin.y -= statusFrame.size.height; + } else { + CGRect viewBounds = self.viewController.view.bounds; + CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height; + CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height; + // This matches the behaviour of the native splash screen. + CGFloat ratio; + if (viewAspect > imgAspect) { + ratio = viewBounds.size.width / imgBounds.size.width; + } else { + ratio = viewBounds.size.height / imgBounds.size.height; } + imgBounds.size.height *= ratio; + imgBounds.size.width *= ratio; } - _imageView.image = [UIImage imageNamed:imageName]; - _imageView.frame = CGRectMake(0, 0, _imageView.image.size.width, _imageView.image.size.height); + _imageView.frame = imgBounds; } -- (void)updateSplashScreenWithState:(int)state +- (void)setVisible:(BOOL)visible { - float toAlpha = state == kSplashScreenStateShow ? 1.0f : 0.0f; - BOOL hidden = state == kSplashScreenStateShow ? NO : YES; + if (visible == _visible) { + return; + } + _visible = visible; id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:@"FadeSplashScreen"]; id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:@"FadeSplashScreenDuration"]; @@ -129,45 +199,26 @@ - (void)updateSplashScreenWithState:(int)state if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { fadeDuration = 0; } - if (hidden && (_imageView == nil)) { - return; - } else if (_imageView == nil) { - [self createViews]; - fadeDuration = 0; - } - - if (!hidden) { - [self updateSplashImageForOrientation:self.viewController.interfaceOrientation]; - } - if (fadeDuration == 0) { - [_imageView setHidden:hidden]; - [_activityView setHidden:hidden]; - } else { - if (state == kSplashScreenStateShow) { - // reset states - [_imageView setHidden:NO]; - [_activityView setHidden:NO]; - [_imageView setAlpha:0.0f]; - [_activityView setAlpha:0.0f]; + // Never animate the showing of the splash screen. + if (visible) { + if (_imageView == nil) { + [self createViews]; } - + } else if (fadeDuration == 0) { + [self destroyViews]; + } else { [UIView transitionWithView:self.viewController.view duration:fadeDuration options:UIViewAnimationOptionTransitionNone animations:^(void) { - [_imageView setAlpha:toAlpha]; - [_activityView setAlpha:toAlpha]; - } + [_imageView setAlpha:0]; + [_activityView setAlpha:0]; + } + completion:^(BOOL finished) { - if (state == kSplashScreenStateHide) { - // Clean-up resources. - [_imageView removeFromSuperview]; - [_activityView removeFromSuperview]; - _imageView = nil; - _activityView = nil; - } - }]; + [self destroyViews]; + }]; } } diff --git a/CordovaLib/Classes/CDVDebugConsole.h b/CordovaLib/Classes/CDVTimer.h similarity index 86% rename from CordovaLib/Classes/CDVDebugConsole.h rename to CordovaLib/Classes/CDVTimer.h index 6a0a185..6d31593 100755 --- a/CordovaLib/Classes/CDVDebugConsole.h +++ b/CordovaLib/Classes/CDVTimer.h @@ -18,11 +18,10 @@ */ #import -#import -#import "CDVPlugin.h" -@interface CDVDebugConsole : CDVPlugin {} +@interface CDVTimer : NSObject -- (void)log:(CDVInvokedUrlCommand*)command; ++ (void)start:(NSString*)name; ++ (void)stop:(NSString*)name; @end diff --git a/CordovaLib/Classes/CDVTimer.m b/CordovaLib/Classes/CDVTimer.m new file mode 100755 index 0000000..784e94d --- /dev/null +++ b/CordovaLib/Classes/CDVTimer.m @@ -0,0 +1,123 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVTimer.h" + +#pragma mark CDVTimerItem + +@interface CDVTimerItem : NSObject + +@property (nonatomic, strong) NSString* name; +@property (nonatomic, strong) NSDate* started; +@property (nonatomic, strong) NSDate* ended; + +- (void)log; + +@end + +@implementation CDVTimerItem + +- (void)log +{ + NSLog(@"[CDVTimer][%@] %fms", self.name, [self.ended timeIntervalSinceDate:self.started] * 1000.0); +} + +@end + +#pragma mark CDVTimer + +@interface CDVTimer () + +@property (nonatomic, strong) NSMutableDictionary* items; + +@end + +@implementation CDVTimer + +#pragma mark object methods + +- (id)init +{ + if (self = [super init]) { + self.items = [NSMutableDictionary dictionaryWithCapacity:6]; + } + + return self; +} + +- (void)add:(NSString*)name +{ + if ([self.items objectForKey:[name lowercaseString]] == nil) { + CDVTimerItem* item = [CDVTimerItem new]; + item.name = name; + item.started = [NSDate new]; + [self.items setObject:item forKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' already exists.", name); + } +} + +- (void)remove:(NSString*)name +{ + CDVTimerItem* item = [self.items objectForKey:[name lowercaseString]]; + + if (item != nil) { + item.ended = [NSDate new]; + [item log]; + [self.items removeObjectForKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' does not exist.", name); + } +} + +- (void)removeAll +{ + [self.items removeAllObjects]; +} + +#pragma mark class methods + ++ (void)start:(NSString*)name +{ + [[CDVTimer sharedInstance] add:name]; +} + ++ (void)stop:(NSString*)name +{ + [[CDVTimer sharedInstance] remove:name]; +} + ++ (void)clearAll +{ + [[CDVTimer sharedInstance] removeAll]; +} + ++ (CDVTimer*)sharedInstance +{ + static dispatch_once_t pred = 0; + __strong static CDVTimer* _sharedObject = nil; + + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; + }); + + return _sharedObject; +} + +@end diff --git a/CordovaLib/Classes/CDVURLProtocol.m b/CordovaLib/Classes/CDVURLProtocol.m index 1959c77..afc10de 100755 --- a/CordovaLib/Classes/CDVURLProtocol.m +++ b/CordovaLib/Classes/CDVURLProtocol.m @@ -31,7 +31,7 @@ @interface CDVHTTPURLResponse : NSHTTPURLResponse @property (nonatomic) NSInteger statusCode; @end -static CDVWhitelist * gWhitelist = nil; +static CDVWhitelist* gWhitelist = nil; // Contains a set of NSNumbers of addresses of controllers. It doesn't store // the actual pointer to avoid retaining. static NSMutableSet* gRegisteredControllers = nil; @@ -159,12 +159,12 @@ - (void)startLoading [self sendResponseWithResponseCode:200 data:nil mimeType:nil]; return; } else if ([[url absoluteString] hasPrefix:kCDVAssetsLibraryPrefix]) { - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and send it along. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [self sendResponseWithResponseCode:200 data:data mimeType:MIMEType]; @@ -173,7 +173,7 @@ - (void)startLoading [self sendResponseWithResponseCode:404 data:nil mimeType:nil]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send an error. [self sendResponseWithResponseCode:401 data:nil mimeType:nil]; }; @@ -202,7 +202,7 @@ - (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mi if (mimeType == nil) { mimeType = @"text/plain"; } - NSString* encodingName = [@"text/plain" isEqualToString:mimeType] ? @"UTF-8" : nil; + NSString* encodingName = [@"text/plain" isEqualToString : mimeType] ? @"UTF-8" : nil; CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithURL:[[self request] URL] MIMEType:mimeType diff --git a/CordovaLib/Classes/CDVUserAgentUtil.m b/CordovaLib/Classes/CDVUserAgentUtil.m index 5c43c51..9923d47 100755 --- a/CordovaLib/Classes/CDVUserAgentUtil.m +++ b/CordovaLib/Classes/CDVUserAgentUtil.m @@ -96,7 +96,7 @@ + (void)releaseLock:(NSInteger*)lockToken void (^block)() = [gPendingSetUserAgentBlocks objectAtIndex:0]; [gPendingSetUserAgentBlocks removeObjectAtIndex:0]; gCurrentLockToken = ++gNextLockToken; - NSLog (@"Gave lock %d", gCurrentLockToken); + NSLog(@"Gave lock %d", gCurrentLockToken); block(gCurrentLockToken); } else { gCurrentLockToken = 0; diff --git a/CordovaLib/Classes/CDVViewController.h b/CordovaLib/Classes/CDVViewController.h index 82e22f6..2338baf 100755 --- a/CordovaLib/Classes/CDVViewController.h +++ b/CordovaLib/Classes/CDVViewController.h @@ -22,15 +22,14 @@ #import "CDVAvailability.h" #import "CDVInvokedUrlCommand.h" #import "CDVCommandDelegate.h" +#import "CDVCommandQueue.h" #import "CDVWhitelist.h" #import "CDVScreenOrientationDelegate.h" - -@class CDVCommandQueue; -@class CDVCommandDelegateImpl; +#import "CDVPlugin.h" @interface CDVViewController : UIViewController { @protected - CDVCommandDelegateImpl* _commandDelegate; + id _commandDelegate; @protected CDVCommandQueue* _commandQueue; NSString* _userAgent; @@ -49,7 +48,7 @@ @property (nonatomic, readwrite, copy) NSString* wwwFolderName; @property (nonatomic, readwrite, copy) NSString* startPage; @property (nonatomic, readonly, strong) CDVCommandQueue* commandQueue; -@property (nonatomic, readonly, strong) CDVCommandDelegateImpl* commandDelegate; +@property (nonatomic, readonly, strong) id commandDelegate; @property (nonatomic, readonly) NSString* userAgent; + (NSDictionary*)getBundlePlist:(NSString*)plistName; @@ -67,6 +66,7 @@ - (id)getCommandInstance:(NSString*)pluginName; - (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className; +- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName; - (BOOL)URLisAllowed:(NSURL*)url; diff --git a/CordovaLib/Classes/CDVViewController.m b/CordovaLib/Classes/CDVViewController.m index bec716d..94f4552 100755 --- a/CordovaLib/Classes/CDVViewController.m +++ b/CordovaLib/Classes/CDVViewController.m @@ -19,7 +19,6 @@ Licensed to the Apache Software Foundation (ASF) under one #import #import "CDV.h" -#import "CDVCommandQueue.h" #import "CDVCommandDelegateImpl.h" #import "CDVConfigParser.h" #import "CDVUserAgentUtil.h" @@ -103,6 +102,48 @@ - (id)init return self; } +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(keyboardWillShowOrHide:) + name:UIKeyboardWillShowNotification + object:nil]; + [nc addObserver:self + selector:@selector(keyboardWillShowOrHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + [nc removeObserver:self name:UIKeyboardWillHideNotification object:nil]; +} + +- (void)keyboardWillShowOrHide:(NSNotification*)notif +{ + if (![@"true" isEqualToString : self.settings[@"KeyboardShrinksView"]]) { + return; + } + BOOL showEvent = [notif.name isEqualToString:UIKeyboardWillShowNotification]; + + CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil]; + + CGRect newFrame = self.view.bounds; + if (showEvent) { + newFrame.size.height -= keyboardFrame.size.height; + } + self.webView.frame = newFrame; + self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, -keyboardFrame.size.height, 0); +} + - (void)printDeprecationNotice { if (!IsAtLeastiOSVersion(@"5.0")) { @@ -208,9 +249,6 @@ - (void)viewDidLoad id backupWebStorage = self.settings[@"BackupWebStorage"]; if ([backupWebStorage isKindOfClass:[NSString class]]) { backupWebStorageType = backupWebStorage; - } else if ([backupWebStorage isKindOfClass:[NSNumber class]]) { - NSLog(@"Deprecated: BackupWebStorage boolean property is a string property now (none, local, cloud). A boolean value of 'true' will be mapped to 'cloud'. Consult the docs: http://docs.cordova.io/en/edge/guide_project-settings_ios_index.md.html#Project%%20Settings%%20for%%20iOS"); - backupWebStorageType = [(NSNumber*) backupWebStorage boolValue] ? @"cloud" : @"none"; } self.settings[@"BackupWebStorage"] = backupWebStorageType; @@ -231,6 +269,10 @@ - (void)viewDidLoad if ([self.settings objectForKey:@"MediaPlaybackRequiresUserAction"]) { mediaPlaybackRequiresUserAction = [(NSNumber*)[settings objectForKey:@"MediaPlaybackRequiresUserAction"] boolValue]; } + BOOL hideKeyboardFormAccessoryBar = NO; // default value + if ([self.settings objectForKey:@"HideKeyboardFormAccessoryBar"]) { + hideKeyboardFormAccessoryBar = [(NSNumber*)[settings objectForKey:@"HideKeyboardFormAccessoryBar"] boolValue]; + } self.webView.scalesPageToFit = [enableViewportScale boolValue]; @@ -239,14 +281,26 @@ - (void)viewDidLoad */ if ([enableLocation boolValue]) { + NSLog(@"Deprecated: The 'EnableLocation' boolean property is deprecated in 2.5.0, and will be removed in 3.0.0. Use the 'onload' boolean attribute (of the CDVLocation plugin."); [[self.commandDelegate getCommandInstance:@"Geolocation"] getLocation:[CDVInvokedUrlCommand new]]; } + if (hideKeyboardFormAccessoryBar) { + __weak CDVViewController* weakSelf = self; + [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* notification) { + // we can't hide it here because the accessory bar hasn't been created yet, so we delay on the queue + [weakSelf performSelector:@selector(hideKeyboardFormAccessoryBar) withObject:nil afterDelay:0]; + }]; + } + /* * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup. */ if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorageType isEqualToString:@"local"]) || - ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { + ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])]; } @@ -260,12 +314,19 @@ - (void)viewDidLoad self.webView.mediaPlaybackRequiresUserAction = NO; } - // UIWebViewBounce property - defaults to true - NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"]; - BOOL bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]); + // By default, overscroll bouncing is allowed. + // UIWebViewBounce has been renamed to DisallowOverscroll, but both are checked. + BOOL bounceAllowed = YES; + NSNumber* disallowOverscroll = [self.settings objectForKey:@"DisallowOverscroll"]; + if (disallowOverscroll == nil) { + NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"]; + bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]); + } else { + bounceAllowed = ![disallowOverscroll boolValue]; + } // prevent webView from bouncing - // based on UIWebViewBounce key in config.xml + // based on the DisallowOverscroll/UIWebViewBounce key in config.xml if (!bounceAllowed) { if ([self.webView respondsToSelector:@selector(scrollView)]) { ((UIScrollView*)[self.webView scrollView]).bounces = NO; @@ -307,8 +368,16 @@ - (void)viewDidLoad } } - for (NSString* pluginName in self.startupPluginNames) { - [self getCommandInstance:pluginName]; + if ([self.startupPluginNames count] > 0) { + [CDVTimer start:@"TotalPluginStartup"]; + + for (NSString* pluginName in self.startupPluginNames) { + [CDVTimer start:pluginName]; + [self getCommandInstance:pluginName]; + [CDVTimer stop:pluginName]; + } + + [CDVTimer stop:@"TotalPluginStartup"]; } // TODO: Remove this explicit instantiation once we move to cordova-CLI. @@ -318,16 +387,44 @@ - (void)viewDidLoad // ///////////////// [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { - _userAgentLockToken = lockToken; - [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; - if (!loadErr) { - NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; - [self.webView loadRequest:appReq]; - } else { - NSString* html = [NSString stringWithFormat:@" %@ ", loadErr]; - [self.webView loadHTMLString:html baseURL:nil]; + _userAgentLockToken = lockToken; + [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; + if (!loadErr) { + NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; + [self.webView loadRequest:appReq]; + } else { + NSString* html = [NSString stringWithFormat:@" %@ ", loadErr]; + [self.webView loadHTMLString:html baseURL:nil]; + } + }]; +} + +- (void)hideKeyboardFormAccessoryBar +{ + NSArray* windows = [[UIApplication sharedApplication] windows]; + + for (UIWindow* window in windows) { + for (UIView* view in window.subviews) { + if ([[view description] hasPrefix:@"*)delegate; diff --git a/CordovaLib/Classes/CDVWebViewDelegate.m b/CordovaLib/Classes/CDVWebViewDelegate.m index 9ee8186..fd9c032 100755 --- a/CordovaLib/Classes/CDVWebViewDelegate.m +++ b/CordovaLib/Classes/CDVWebViewDelegate.m @@ -17,14 +17,77 @@ Licensed to the Apache Software Foundation (ASF) under one under the License. */ +// +// Testing shows: +// +// In all cases, webView.request.URL is the previous page's URL (or empty) during the didStartLoad callback. +// When loading a page with a redirect: +// 1. shouldStartLoading (requestURL is target page) +// 2. didStartLoading +// 3. shouldStartLoading (requestURL is redirect target) +// 4. didFinishLoad (request.URL is redirect target) +// +// Note the lack of a second didStartLoading ** +// +// When loading a page with iframes: +// 1. shouldStartLoading (requestURL is main page) +// 2. didStartLoading +// 3. shouldStartLoading (requestURL is one of the iframes) +// 4. didStartLoading +// 5. didFinishLoad +// 6. didFinishLoad +// +// Note there is no way to distinguish which didFinishLoad maps to which didStartLoad ** +// +// Loading a page by calling window.history.go(-1): +// 1. didStartLoading +// 2. didFinishLoad +// +// Note the lack of a shouldStartLoading call ** +// Actually - this is fixed on iOS6. iOS6 has a shouldStart. ** +// +// Loading a page by calling location.reload() +// 1. shouldStartLoading +// 2. didStartLoading +// 3. didFinishLoad +// +// Loading a page with an iframe that fails to load: +// 1. shouldStart (main page) +// 2. didStart +// 3. shouldStart (iframe) +// 4. didStart +// 5. didFailWithError +// 6. didFinish +// +// Loading a page with an iframe that fails to load due to an invalid URL: +// 1. shouldStart (main page) +// 2. didStart +// 3. shouldStart (iframe) +// 5. didFailWithError +// 6. didFinish +// +// This case breaks our logic since there is a missing didStart. To prevent this, +// we check URLs in shouldStart and return NO if they are invalid. +// +// Loading a page with an invalid URL +// 1. shouldStart (main page) +// 2. didFailWithError +// +// TODO: Record order when page is re-navigated before the first navigation finishes. +// + #import "CDVWebViewDelegate.h" #import "CDVAvailability.h" +// #define VerboseLog NSLog +#define VerboseLog(...) do {} while (0) + typedef enum { - STATE_NORMAL, - STATE_SHOULD_LOAD_MISSING, - STATE_WAITING_FOR_START, - STATE_WAITING_FOR_FINISH + STATE_IDLE, + STATE_WAITING_FOR_LOAD_START, + STATE_WAITING_FOR_LOAD_FINISH, + STATE_IOS5_POLLING_FOR_LOAD_START, + STATE_IOS5_POLLING_FOR_LOAD_FINISH } State; @implementation CDVWebViewDelegate @@ -35,7 +98,7 @@ - (id)initWithDelegate:(NSObject *)delegate if (self != nil) { _delegate = delegate; _loadCount = -1; - _state = STATE_NORMAL; + _state = STATE_IDLE; } return self; } @@ -62,95 +125,209 @@ - (void)setLoadToken:(UIWebView*)webView - (void)pollForPageLoadStart:(UIWebView*)webView { - if ((_state != STATE_WAITING_FOR_START) && (_state != STATE_SHOULD_LOAD_MISSING)) { + if (_state != STATE_IOS5_POLLING_FOR_LOAD_START) { return; } if (![self isJsLoadTokenSet:webView]) { - _state = STATE_WAITING_FOR_FINISH; + VerboseLog(@"Polled for page load start. result = YES!"); + _state = STATE_IOS5_POLLING_FOR_LOAD_FINISH; [self setLoadToken:webView]; - [_delegate webViewDidStartLoad:webView]; + if ([_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [_delegate webViewDidStartLoad:webView]; + } [self pollForPageLoadFinish:webView]; + } else { + VerboseLog(@"Polled for page load start. result = NO"); + // Poll only for 1 second, and then fall back on checking only when delegate methods are called. + ++_loadStartPollCount; + if (_loadStartPollCount < (1000 * .05)) { + [self performSelector:@selector(pollForPageLoadStart:) withObject:webView afterDelay:.05]; + } } } - (void)pollForPageLoadFinish:(UIWebView*)webView { - if (_state != STATE_WAITING_FOR_FINISH) { + if (_state != STATE_IOS5_POLLING_FOR_LOAD_FINISH) { return; } if ([self isPageLoaded:webView]) { - _state = STATE_SHOULD_LOAD_MISSING; - [_delegate webViewDidFinishLoad:webView]; + VerboseLog(@"Polled for page load finish. result = YES!"); + _state = STATE_IDLE; + if ([_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [_delegate webViewDidFinishLoad:webView]; + } } else { - [self performSelector:@selector(pollForPageLoaded) withObject:webView afterDelay:50]; + VerboseLog(@"Polled for page load finish. result = NO"); + [self performSelector:@selector(pollForPageLoadFinish:) withObject:webView afterDelay:.05]; } } - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType { - BOOL shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + BOOL shouldLoad = YES; + + if ([_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { + shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + } + + VerboseLog(@"webView shouldLoad=%d (before) state=%d loadCount=%d URL=%@", shouldLoad, _state, _loadCount, request.URL); if (shouldLoad) { BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; if (isTopLevelNavigation) { - _loadCount = 0; - _state = STATE_NORMAL; + switch (_state) { + case STATE_WAITING_FOR_LOAD_FINISH: + // Redirect case. + // We expect loadCount == 1. + if (_loadCount != 1) { + NSLog(@"CDVWebViewDelegate: Detected redirect when loadCount=%d", _loadCount); + } + break; + + case STATE_IDLE: + case STATE_IOS5_POLLING_FOR_LOAD_START: + // Page navigation start. + _loadCount = 0; + _state = STATE_WAITING_FOR_LOAD_START; + break; + + default: + NSLog(@"CDVWebViewDelegate: Navigation started when state=%d", _state); + _loadCount = 0; + _state = STATE_WAITING_FOR_LOAD_START; + if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [_delegate webView:webView didFailLoadWithError:nil]; + } + } + } else { + // Deny invalid URLs so that we don't get the case where we go straight from + // webViewShouldLoad -> webViewDidFailLoad (messes up _loadCount). + shouldLoad = shouldLoad && [NSURLConnection canHandleRequest:request]; } + VerboseLog(@"webView shouldLoad=%d (after) isTopLevelNavigation=%d state=%d loadCount=%d", shouldLoad, isTopLevelNavigation, _state, _loadCount); } return shouldLoad; } - (void)webViewDidStartLoad:(UIWebView*)webView { - if (_state == STATE_NORMAL) { - if (_loadCount == 0) { - [_delegate webViewDidStartLoad:webView]; - _loadCount += 1; - } else if (_loadCount > 0) { - _loadCount += 1; - } else if (!IsAtLeastiOSVersion(@"6.0")) { + VerboseLog(@"webView didStartLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + switch (_state) { + case STATE_IDLE: + if (IsAtLeastiOSVersion(@"6.0")) { + break; + } // If history.go(-1) is used pre-iOS6, the shouldStartLoadWithRequest function is not called. // Without shouldLoad, we can't distinguish an iframe from a top-level navigation. // We could try to distinguish using [UIWebView canGoForward], but that's too much complexity, // and would work only on the first time it was used. // Our work-around is to set a JS variable and poll until it disappears (from a naviagtion). - _state = STATE_WAITING_FOR_START; + _state = STATE_IOS5_POLLING_FOR_LOAD_START; + _loadStartPollCount = 0; [self setLoadToken:webView]; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + [self pollForPageLoadStart:webView]; + break; + + case STATE_WAITING_FOR_LOAD_START: + if (_loadCount != 0) { + NSLog(@"CDVWebViewDelegate: Unexpected loadCount in didStart. count=%d", _loadCount); + } + fireCallback = YES; + _state = STATE_WAITING_FOR_LOAD_FINISH; + _loadCount = 1; + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + _loadCount += 1; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + + default: + NSLog(@"CDVWebViewDelegate: Unexpected didStart with state=%d loadCount=%d", _state, _loadCount); + } + VerboseLog(@"webView didStartLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [_delegate webViewDidStartLoad:webView]; } } - (void)webViewDidFinishLoad:(UIWebView*)webView { - if (_state == STATE_NORMAL) { - if (_loadCount == 1) { - [_delegate webViewDidFinishLoad:webView]; - _loadCount -= 1; - } else if (_loadCount > 1) { + VerboseLog(@"webView didFinishLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + switch (_state) { + case STATE_IDLE: + break; + + case STATE_WAITING_FOR_LOAD_START: + NSLog(@"CDVWebViewDelegate: Unexpected didFinish while waiting for load start."); + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + if (_loadCount == 1) { + fireCallback = YES; + _state = STATE_IDLE; + } _loadCount -= 1; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + } + VerboseLog(@"webView didFinishLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [_delegate webViewDidFinishLoad:webView]; } } - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error { - if (_state == STATE_NORMAL) { - if (_loadCount == 1) { - [_delegate webView:webView didFailLoadWithError:error]; - _loadCount -= 1; - } else if (_loadCount > 1) { - _loadCount -= 1; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + VerboseLog(@"webView didFailLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + + switch (_state) { + case STATE_IDLE: + break; + + case STATE_WAITING_FOR_LOAD_START: + _state = STATE_IDLE; + fireCallback = YES; + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + if (_loadCount == 1) { + _state = STATE_IDLE; + fireCallback = YES; + } + _loadCount = -1; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + } + VerboseLog(@"webView didFailLoad (after). state=%d loadCount=%d, fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [_delegate webView:webView didFailLoadWithError:error]; } } diff --git a/CordovaLib/Classes/CDVWhitelist.h b/CordovaLib/Classes/CDVWhitelist.h index 3741e94..e339dd0 100755 --- a/CordovaLib/Classes/CDVWhitelist.h +++ b/CordovaLib/Classes/CDVWhitelist.h @@ -23,14 +23,11 @@ extern NSString* const kCDVDefaultWhitelistRejectionString; @interface CDVWhitelist : NSObject -@property (nonatomic, readonly, strong) NSArray* whitelist; -@property (nonatomic, readonly, strong) NSArray* expandedWhitelist; -@property (nonatomic, readonly, assign) BOOL allowAll; @property (nonatomic, copy) NSString* whitelistRejectionFormatString; - (id)initWithArray:(NSArray*)array; -- (BOOL)URLIsAllowed:(NSURL*)url; - (BOOL)schemeIsAllowed:(NSString*)scheme; +- (BOOL)URLIsAllowed:(NSURL*)url; - (NSString*)errorStringForURL:(NSURL*)url; @end diff --git a/CordovaLib/Classes/CDVWhitelist.m b/CordovaLib/Classes/CDVWhitelist.m index 77e20ac..db7aa32 100755 --- a/CordovaLib/Classes/CDVWhitelist.m +++ b/CordovaLib/Classes/CDVWhitelist.m @@ -20,12 +20,12 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVWhitelist.h" NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejection: url='%@'"; +NSString* const kCDVDefaultSchemeName = @"cdv-default-scheme"; @interface CDVWhitelist () @property (nonatomic, readwrite, strong) NSArray* whitelist; -@property (nonatomic, readwrite, strong) NSArray* expandedWhitelist; -@property (nonatomic, readwrite, assign) BOOL allowAll; +@property (nonatomic, readwrite, strong) NSDictionary* expandedWhitelists; - (void)processWhitelist; @@ -33,15 +33,14 @@ - (void)processWhitelist; @implementation CDVWhitelist -@synthesize whitelist, expandedWhitelist, allowAll, whitelistRejectionFormatString; +@synthesize whitelist, expandedWhitelists, whitelistRejectionFormatString; - (id)initWithArray:(NSArray*)array { self = [super init]; if (self) { self.whitelist = array; - self.expandedWhitelist = nil; - self.allowAll = NO; + self.expandedWhitelists = nil; self.whitelistRejectionFormatString = kCDVDefaultWhitelistRejectionString; [self processWhitelist]; } @@ -94,6 +93,17 @@ - (NSString*)extractHostFromUrlString:(NSString*)url } } +- (NSString*)extractSchemeFromUrlString:(NSString*)url +{ + NSURL* aUrl = [NSURL URLWithString:url]; + + if ((aUrl != nil) && ([aUrl scheme] != nil)) { // found scheme + return [aUrl scheme]; + } else { + return kCDVDefaultSchemeName; + } +} + - (void)processWhitelist { if (self.whitelist == nil) { @@ -101,70 +111,91 @@ - (void)processWhitelist return; } - NSMutableArray* expanded = [NSMutableArray arrayWithCapacity:[self.whitelist count]]; - - // iterate through settings ExternalHosts, check for equality - NSEnumerator* enumerator = [self.whitelist objectEnumerator]; - id externalHost = nil; + NSMutableDictionary* _expandedWhitelists = [@{kCDVDefaultSchemeName: [NSMutableArray array]} mutableCopy]; // only allow known TLDs (since Aug 23rd 2011), and two character country codes // does not match internationalized domain names with non-ASCII characters NSString* tld_match = @"(aero|asia|arpa|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|[a-z][a-z])"; - while (externalHost = [enumerator nextObject]) { - NSString* regex = [self extractHostFromUrlString:externalHost]; - BOOL is_ip = [self isIPv4Address:regex]; + // iterate through settings ExternalHosts, check for equality + for (NSString* externalHost in self.whitelist) { + NSString* host = [self extractHostFromUrlString:externalHost]; + NSString* scheme = [self extractSchemeFromUrlString:externalHost]; // check for single wildcard '*', if found set allowAll to YES - if ([regex isEqualToString:@"*"]) { - self.allowAll = YES; - self.expandedWhitelist = [NSArray arrayWithObject:regex]; - break; + if ([host isEqualToString:@"*"]) { + [_expandedWhitelists setObject:[NSArray arrayWithObject:host] forKey:scheme]; + continue; + } + + // if this is the first value for this scheme, create a new entry + if ([_expandedWhitelists objectForKey:scheme] == nil) { + [_expandedWhitelists setObject:[NSMutableArray array] forKey:scheme]; } // starts with wildcard match - we make the first '.' optional (so '*.org.apache.cordova' will match 'org.apache.cordova') NSString* prefix = @"*."; - if ([regex hasPrefix:prefix]) { + if ([host hasPrefix:prefix]) { // replace the first two characters '*.' with our regex - regex = [regex stringByReplacingCharactersInRange:NSMakeRange(0, [prefix length]) withString:@"(\\s{0}|*.)"]; // the '*' and '.' will be substituted later + host = [host stringByReplacingCharactersInRange:NSMakeRange(0, [prefix length]) withString:@"(\\s{0}|*.)"]; // the '*' and '.' will be substituted later } // ends with wildcard match for TLD - if (!is_ip && [regex hasSuffix:@".*"]) { + if (![self isIPv4Address:host] && [host hasSuffix:@".*"]) { // replace * with tld_match - regex = [regex stringByReplacingCharactersInRange:NSMakeRange([regex length] - 1, 1) withString:tld_match]; + host = [host stringByReplacingCharactersInRange:NSMakeRange([host length] - 1, 1) withString:tld_match]; } // escape periods - since '.' means any character in regex - regex = [regex stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; + host = [host stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; // wildcard is match 1 or more characters (to make it simple, since we are not doing verification whether the hostname is valid) - regex = [regex stringByReplacingOccurrencesOfString:@"*" withString:@".*"]; + host = [host stringByReplacingOccurrencesOfString:@"*" withString:@".*"]; - [expanded addObject:regex]; + [[_expandedWhitelists objectForKey:scheme] addObject:host]; } - self.expandedWhitelist = expanded; + self.expandedWhitelists = _expandedWhitelists; } - (BOOL)schemeIsAllowed:(NSString*)scheme { - return [scheme isEqualToString:@"http"] || - [scheme isEqualToString:@"https"] || - [scheme isEqualToString:@"ftp"] || - [scheme isEqualToString:@"ftps"]; + if ([scheme isEqualToString:@"http"] || + [scheme isEqualToString:@"https"] || + [scheme isEqualToString:@"ftp"] || + [scheme isEqualToString:@"ftps"]) { + return YES; + } + + return (self.expandedWhitelists != nil) && ([self.expandedWhitelists objectForKey:scheme] != nil); } - (BOOL)URLIsAllowed:(NSURL*)url { - if (self.expandedWhitelist == nil) { + NSString* scheme = [url scheme]; + + // http[s] and ftp[s] should also validate against the common set in the kCDVDefaultSchemeName list + if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"] || [scheme isEqualToString:@"ftps"]) { + NSURL* newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", kCDVDefaultSchemeName, [url host]]]; + // If it is allowed, we are done. If not, continue to check for the actual scheme-specific list + if ([self URLIsAllowed:newUrl]) { + return YES; + } + } + + // Check that the scheme is supported + if (![self schemeIsAllowed:scheme]) { return NO; } - if (self.allowAll) { + NSArray* expandedWhitelist = [self.expandedWhitelists objectForKey:scheme]; + + // Are we allowing everything for this scheme? + // TODO: consider just having a static sentinel value for the "allow all" list, so we can use object equality + if (([expandedWhitelist count] == 1) && [[expandedWhitelist objectAtIndex:0] isEqualToString:@"*"]) { return YES; } // iterate through settings ExternalHosts, check for equality - NSEnumerator* enumerator = [self.expandedWhitelist objectEnumerator]; + NSEnumerator* enumerator = [expandedWhitelist objectEnumerator]; id regex = nil; NSString* urlHost = [url host]; diff --git a/CordovaLib/Classes/NSData+Base64.m b/CordovaLib/Classes/NSData+Base64.m index 08c801b..d0f2189 100755 --- a/CordovaLib/Classes/NSData+Base64.m +++ b/CordovaLib/Classes/NSData+Base64.m @@ -249,13 +249,10 @@ @implementation NSData (CDVBase64) // + (NSData*)dataFromBase64String:(NSString*)aString { - NSData* data = [aString dataUsingEncoding:NSASCIIStringEncoding]; - size_t outputLength; - void* outputBuffer = CDVNewBase64Decode([data bytes], [data length], &outputLength); - NSData* result = [NSData dataWithBytes:outputBuffer length:outputLength]; + size_t outputLength = 0; + void* outputBuffer = CDVNewBase64Decode([aString UTF8String], [aString length], &outputLength); - free(outputBuffer); - return result; + return [NSData dataWithBytesNoCopy:outputBuffer length:outputLength freeWhenDone:YES]; } // @@ -273,13 +270,11 @@ - (NSString*)base64EncodedString char* outputBuffer = CDVNewBase64Encode([self bytes], [self length], true, &outputLength); - NSString* result = - [[NSString alloc] - initWithBytes:outputBuffer - length:outputLength - encoding:NSASCIIStringEncoding]; + NSString* result = [[NSString alloc] initWithBytesNoCopy:outputBuffer + length:outputLength + encoding:NSASCIIStringEncoding + freeWhenDone:YES]; - free(outputBuffer); return result; } diff --git a/CordovaLib/Classes/NSDictionary+Extensions.m b/CordovaLib/Classes/NSDictionary+Extensions.m index 80e9ac1..0361ff9 100755 --- a/CordovaLib/Classes/NSDictionary+Extensions.m +++ b/CordovaLib/Classes/NSDictionary+Extensions.m @@ -28,7 +28,7 @@ - (bool)existsValue:(NSString*)expectedValue forKey:(NSString*)key bool exists = false; if (val != nil) { - exists = [(NSString*) val compare:expectedValue options:NSCaseInsensitiveSearch] == 0; + exists = [(NSString*)val compare : expectedValue options : NSCaseInsensitiveSearch] == 0; } return exists; diff --git a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index 4868020..c637d60 100755 --- a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -44,6 +44,11 @@ 30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */; }; 3E76876F156A90EE00EB6FA3 /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B7516E16FD18190076A8B4 /* CDVJpegHeaderWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B7516F16FD18190076A8B4 /* CDVJpegHeaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */; }; + 68B7517016FD19F80076A8B4 /* CDVExif.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B7516A16FD18190076A8B4 /* CDVExif.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E14B5A81705050A0032169E /* CDVTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E14B5A61705050A0032169E /* CDVTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E14B5A91705050A0032169E /* CDVTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E14B5A71705050A0032169E /* CDVTimer.m */; }; 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43614B65FD800F0E735 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43714B65FD800F0E735 /* CDVViewController.m */; }; 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD261090FBE7009987E8 /* CDVCamera.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -52,8 +57,6 @@ 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */; }; 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2A1090FBE7009987E8 /* CDVContacts.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2B1090FBE7009987E8 /* CDVContacts.m */; }; - 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */; }; 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD301090FBE7009987E8 /* CDVFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD311090FBE7009987E8 /* CDVFile.m */; }; 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -141,6 +144,12 @@ 686357DC14100B1600DF4CF2 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 68A32D7114102E1C006B237C /* libCordova.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCordova.a; sourceTree = BUILT_PRODUCTS_DIR; }; 68A32D7414103017006B237C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + 68B7516A16FD18190076A8B4 /* CDVExif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVExif.h; path = Classes/CDVExif.h; sourceTree = ""; }; + 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVJpegHeaderWriter.h; path = Classes/CDVJpegHeaderWriter.h; sourceTree = ""; }; + 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVJpegHeaderWriter.m; path = Classes/CDVJpegHeaderWriter.m; sourceTree = ""; }; + 7E14B5A61705050A0032169E /* CDVTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVTimer.h; path = Classes/CDVTimer.h; sourceTree = ""; }; + 7E14B5A71705050A0032169E /* CDVTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVTimer.m; path = Classes/CDVTimer.m; sourceTree = ""; }; + 8220B5C316D5427E00EC3921 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 8852C43614B65FD800F0E735 /* CDVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVViewController.h; path = Classes/CDVViewController.h; sourceTree = ""; }; 8852C43714B65FD800F0E735 /* CDVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVViewController.m; path = Classes/CDVViewController.m; sourceTree = ""; }; 8887FD261090FBE7009987E8 /* CDVCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCamera.h; path = Classes/CDVCamera.h; sourceTree = ""; }; @@ -149,8 +158,6 @@ 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Extensions.m"; path = "Classes/NSDictionary+Extensions.m"; sourceTree = ""; }; 8887FD2A1090FBE7009987E8 /* CDVContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVContacts.h; path = Classes/CDVContacts.h; sourceTree = ""; }; 8887FD2B1090FBE7009987E8 /* CDVContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVContacts.m; path = Classes/CDVContacts.m; sourceTree = ""; }; - 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDebugConsole.h; path = Classes/CDVDebugConsole.h; sourceTree = ""; }; - 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVDebugConsole.m; path = Classes/CDVDebugConsole.m; sourceTree = ""; }; 8887FD301090FBE7009987E8 /* CDVFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVFile.h; path = Classes/CDVFile.h; sourceTree = ""; }; 8887FD311090FBE7009987E8 /* CDVFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVFile.m; path = Classes/CDVFile.m; sourceTree = ""; }; 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVInvokedUrlCommand.h; path = Classes/CDVInvokedUrlCommand.h; sourceTree = ""; }; @@ -223,6 +230,7 @@ isa = PBXGroup; children = ( 68A32D7414103017006B237C /* AddressBook.framework */, + 8220B5C316D5427E00EC3921 /* AssetsLibrary.framework */, 686357DC14100B1600DF4CF2 /* CoreMedia.framework */, 686357CE14100ADA00DF4CF2 /* AudioToolbox.framework */, 686357CF14100ADB00DF4CF2 /* AVFoundation.framework */, @@ -292,12 +300,13 @@ 8887FD271090FBE7009987E8 /* CDVCamera.m */, 1F584B991385A28900ED25E8 /* CDVCapture.h */, 1F584B9A1385A28900ED25E8 /* CDVCapture.m */, + 68B7516A16FD18190076A8B4 /* CDVExif.h */, + 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */, + 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */, 1F3C04CC12BC247D004F9E10 /* CDVContact.h */, 1F3C04CD12BC247D004F9E10 /* CDVContact.m */, 8887FD2A1090FBE7009987E8 /* CDVContacts.h */, 8887FD2B1090FBE7009987E8 /* CDVContacts.m */, - 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */, - 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */, EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */, EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */, 8887FD301090FBE7009987E8 /* CDVFile.h */, @@ -343,6 +352,8 @@ 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */, 8887FD501090FBE7009987E8 /* NSData+Base64.h */, 8887FD511090FBE7009987E8 /* NSData+Base64.m */, + 7E14B5A61705050A0032169E /* CDVTimer.h */, + 7E14B5A71705050A0032169E /* CDVTimer.m */, ); name = Util; sourceTree = ""; @@ -364,10 +375,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 68B7517016FD19F80076A8B4 /* CDVExif.h in Headers */, + 68B7516E16FD18190076A8B4 /* CDVJpegHeaderWriter.h in Headers */, 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */, 8887FD681090FBE7009987E8 /* NSDictionary+Extensions.h in Headers */, 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */, - 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */, 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */, 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */, 8887FD851090FBE7009987E8 /* CDVLocation.h in Headers */, @@ -407,6 +419,7 @@ 30F3930B169F839700B22307 /* CDVJSON.h in Headers */, EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */, EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */, + 7E14B5A81705050A0032169E /* CDVTimer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -467,7 +480,6 @@ 8887FD671090FBE7009987E8 /* CDVCamera.m in Sources */, 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */, 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */, - 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */, 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */, 8887FD751090FBE7009987E8 /* CDVInvokedUrlCommand.m in Sources */, 8887FD861090FBE7009987E8 /* CDVLocation.m in Sources */, @@ -502,6 +514,8 @@ 30F3930C169F839700B22307 /* CDVJSON.m in Sources */, EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */, EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */, + 68B7516F16FD18190076A8B4 /* CDVJpegHeaderWriter.m in Sources */, + 7E14B5A91705050A0032169E /* CDVTimer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CordovaLib/VERSION b/CordovaLib/VERSION index 437459c..24ba9a3 100755 --- a/CordovaLib/VERSION +++ b/CordovaLib/VERSION @@ -1 +1 @@ -2.5.0 +2.7.0 diff --git a/Default-568h@2x.png b/Default-568h@2x.png index b5d6211..4200c36 100644 Binary files a/Default-568h@2x.png and b/Default-568h@2x.png differ diff --git a/Default.png b/Default.png index 38758c9..db4a9f5 100644 Binary files a/Default.png and b/Default.png differ diff --git a/Default@2x.png b/Default@2x.png index 8dea2cf..bf2352b 100644 Binary files a/Default@2x.png and b/Default@2x.png differ diff --git a/TDA-Enrichment/Classes/AppDelegate.m b/TDA-Enrichment/Classes/AppDelegate.m index bdac781..39b592f 100644 --- a/TDA-Enrichment/Classes/AppDelegate.m +++ b/TDA-Enrichment/Classes/AppDelegate.m @@ -19,7 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one // // AppDelegate.m -// cordova-2.5.0 +// cordova-2.7.0 // // Created by George Garside on 11/01/2013. // Copyright George Garside 2012-2013. All rights reserved. @@ -47,7 +47,11 @@ - (id)init int cacheSizeMemory = 8 * 1024 * 1024; // 8MB int cacheSizeDisk = 32 * 1024 * 1024; // 32MB - NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"]; +#if __has_feature(objc_arc) + NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"]; +#else + NSURLCache* sharedCache = [[[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"] autorelease]; +#endif [NSURLCache setSharedURLCache:sharedCache]; self = [super init]; @@ -66,10 +70,19 @@ - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(N CGRect screenBounds = [[UIScreen mainScreen] bounds]; - self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease]; +#if __has_feature(objc_arc) + self.window = [[UIWindow alloc] initWithFrame:screenBounds]; +#else + self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease]; +#endif self.window.autoresizesSubviews = YES; - self.viewController = [[[MainViewController alloc] init] autorelease]; +#if __has_feature(objc_arc) + self.viewController = [[MainViewController alloc] init]; +#else + self.viewController = [[[MainViewController alloc] init] autorelease]; +#endif + self.viewController.useSplashScreen = YES; // Set your app's start page by setting the tag in config.xml. // If necessary, uncomment the line below to override it. @@ -85,7 +98,7 @@ - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(N } // this happens while we are running ( in the background, or from within our own app ) -// only valid if cordova-2.5.0-Info.plist specifies a protocol to handle +// only valid if cordova-2.7.0-Info.plist specifies a protocol to handle - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { if (!url) { @@ -103,8 +116,8 @@ - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url } // repost the localnotification using the default NSNotificationCenter so multiple plugins may respond -- (void) application:(UIApplication*)application - didReceiveLocalNotification:(UILocalNotification*)notification +- (void) application:(UIApplication*)application + didReceiveLocalNotification:(UILocalNotification*)notification { // re-post ( broadcast ) [[NSNotificationCenter defaultCenter] postNotificationName:CDVLocalNotification object:notification]; diff --git a/TDA-Enrichment/Cordova.plist b/TDA-Enrichment/Cordova.plist new file mode 100755 index 0000000..4a451fa --- /dev/null +++ b/TDA-Enrichment/Cordova.plist @@ -0,0 +1,76 @@ + + + + + KeyboardDisplayRequiresUserAction + + SuppressesIncrementalRendering + + UIWebViewBounce + + TopActivityIndicator + gray + EnableLocation + + EnableViewportScale + + AutoHideSplashScreen + + ShowSplashScreenSpinner + + MediaPlaybackRequiresUserAction + + AllowInlineMediaPlayback + + OpenAllWhitelistURLsInWebView + + BackupWebStorage + cloud + ExternalHosts + + *.testflightapp.com + *.georgegarside.x10.bz + *.thomasdeaconacademy.com + *.thomasdeaconacademy.peterborough.sch.uk + + Plugins + + Device + CDVDevice + Logger + CDVLogger + Compass + CDVLocation + Accelerometer + CDVAccelerometer + Camera + CDVCamera + NetworkStatus + CDVConnection + Contacts + CDVContacts + Debug Console + CDVDebugConsole + Echo + CDVEcho + File + CDVFile + FileTransfer + CDVFileTransfer + Geolocation + CDVLocation + Notification + CDVNotification + Media + CDVSound + Capture + CDVCapture + SplashScreen + CDVSplashScreen + Battery + CDVBattery + Globalization + CDVGlobalization + + + diff --git a/TDA-Enrichment/TDA-Enrichment-Info.plist b/TDA-Enrichment/TDA-Enrichment-Info.plist index 9dcff18..c098353 100644 --- a/TDA-Enrichment/TDA-Enrichment-Info.plist +++ b/TDA-Enrichment/TDA-Enrichment-Info.plist @@ -39,7 +39,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.3 + 1.2 CFBundleSignature ???? CFBundleURLTypes @@ -58,7 +58,7 @@ CFBundleVersion - 60 + 113 LSRequiresIPhoneOS NSMainNibFile~ipad diff --git a/TDA-Enrichment/config.xml b/TDA-Enrichment/config.xml index b7c24ae..ed3a2f3 100644 --- a/TDA-Enrichment/config.xml +++ b/TDA-Enrichment/config.xml @@ -3,16 +3,17 @@ - + + + - @@ -24,7 +25,6 @@ - @@ -45,4 +45,5 @@ + diff --git a/www/cordova-2.5.0.js b/www/cordova-2.7.0.js similarity index 90% rename from www/cordova-2.5.0.js rename to www/cordova-2.7.0.js index 3d83df3..b716939 100755 --- a/www/cordova-2.5.0.js +++ b/www/cordova-2.7.0.js @@ -1,8 +1,8 @@ // Platform: ios -// commit f50d20a87431c79a54572263729461883f611a53 +// commit cd29cf0f224ccf25e9d422a33fd02ef67d3a78f4 -// File generated at :: Tue Feb 26 2013 14:26:19 GMT-0800 (PST) +// File generated at :: Mon Apr 29 2013 16:14:47 GMT-0700 (PDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -219,6 +219,10 @@ var cordova = { } else { setTimeout(function() { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type == 'deviceready') { + document.dispatchEvent(evt); + } documentEventHandlers[type].fire(evt); }, 0); } @@ -262,7 +266,7 @@ var cordova = { */ callbackSuccess: function(callbackId, args) { try { - cordova.callbackFromNative(callbackId, true, args.status, args.message, args.keepCallback); + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); } catch (e) { console.log("Error in error callback: " + callbackId + " = "+e); } @@ -275,7 +279,7 @@ var cordova = { // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. // Derive success from status. try { - cordova.callbackFromNative(callbackId, false, args.status, args.message, args.keepCallback); + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); } catch (e) { console.log("Error in error callback: " + callbackId + " = "+e); } @@ -284,13 +288,13 @@ var cordova = { /** * Called by native code when returning the result from an action. */ - callbackFromNative: function(callbackId, success, status, message, keepCallback) { + callbackFromNative: function(callbackId, success, status, args, keepCallback) { var callback = cordova.callbacks[callbackId]; if (callback) { if (success && status == cordova.callbackStatus.OK) { - callback.success && callback.success(message); + callback.success && callback.success.apply(null, args); } else if (!success) { - callback.fail && callback.fail(message); + callback.fail && callback.fail.apply(null, args); } // Clear callback if not expecting any more results @@ -724,6 +728,9 @@ channel.createSticky('onCordovaInfoReady'); // Event to indicate that the connection property has been set. channel.createSticky('onCordovaConnectionReady'); +// Event to indicate that all automatically loaded JS plugins are loaded and ready. +channel.createSticky('onPluginsReady'); + // Event to indicate that Cordova is ready channel.createSticky('onDeviceReady'); @@ -739,6 +746,7 @@ channel.createSticky('onDestroy'); // Channels that must fire before "deviceready" is fired. channel.waitForInitialization('onCordovaReady'); channel.waitForInitialization('onCordovaConnectionReady'); +channel.waitForInitialization('onDOMContentLoaded'); module.exports = channel; @@ -826,6 +834,7 @@ function massageArgsJsToNative(args) { if (!args || utils.typeName(args) != 'Array') { return args; } + var ret = []; var encodeArrayBufferAs8bitString = function(ab) { return String.fromCharCode.apply(null, new Uint8Array(ab)); }; @@ -834,17 +843,19 @@ function massageArgsJsToNative(args) { }; args.forEach(function(arg, i) { if (utils.typeName(arg) == 'ArrayBuffer') { - args[i] = { + ret.push({ 'CDVType': 'ArrayBuffer', 'data': encodeArrayBufferAsBase64(arg) - }; + }); + } else { + ret.push(arg); } }); - return args; + return ret; } -function massagePayloadNativeToJs(payload) { - if (payload && payload.hasOwnProperty('CDVType') && payload.CDVType == 'ArrayBuffer') { +function massageMessageNativeToJs(message) { + if (message.CDVType == 'ArrayBuffer') { var stringToArrayBuffer = function(str) { var ret = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { @@ -855,9 +866,23 @@ function massagePayloadNativeToJs(payload) { var base64ToArrayBuffer = function(b64) { return stringToArrayBuffer(atob(b64)); }; - payload = base64ToArrayBuffer(payload.data); + message = base64ToArrayBuffer(message.data); } - return payload; + return message; +} + +function convertMessageToArgsNativeToJs(message) { + var args = []; + if (!message || !message.hasOwnProperty('CDVType')) { + args.push(message); + } else if (message.CDVType == 'MultiPart') { + message.messages.forEach(function(e) { + args.push(massageMessageNativeToJs(e)); + }); + } else { + args.push(massageMessageNativeToJs(message)); + } + return args; } function iOSExec() { @@ -884,11 +909,19 @@ function iOSExec() { // an invalid callbackId and passes it even if no callbacks were given. callbackId = 'INVALID'; } else { - // FORMAT TWO - splitCommand = arguments[0].split("."); - action = splitCommand.pop(); - service = splitCommand.join("."); - actionArgs = Array.prototype.splice.call(arguments, 1); + // FORMAT TWO, REMOVED + try { + splitCommand = arguments[0].split("."); + action = splitCommand.pop(); + service = splitCommand.join("."); + actionArgs = Array.prototype.splice.call(arguments, 1); + + console.log('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + + "cordova.exec(null, null, \"" + service + "\", " + action + "\"," + JSON.stringify(actionArgs) + ");" + ); + return; + } catch (e) { + } } // Register the callbacks and add the callbackId to the positional @@ -964,11 +997,11 @@ iOSExec.nativeFetchMessages = function() { return json; }; -iOSExec.nativeCallback = function(callbackId, status, payload, keepCallback) { +iOSExec.nativeCallback = function(callbackId, status, message, keepCallback) { return iOSExec.nativeEvalAndFetch(function() { var success = status === 0 || status === 1; - payload = massagePayloadNativeToJs(payload); - cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); + var args = convertMessageToArgsNativeToJs(message); + cordova.callbackFromNative(callbackId, success, status, args, keepCallback); }); }; @@ -1161,9 +1194,10 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) { var correctOrientation = !!options.correctOrientation; var saveToPhotoAlbum = !!options.saveToPhotoAlbum; var popoverOptions = getValue(options.popoverOptions, null); + var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, - mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions]; + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection]; exec(successCallback, errorCallback, "Camera", "takePicture", args); return new CameraPopoverHandle(); @@ -1206,6 +1240,10 @@ module.exports = { ARROW_LEFT : 4, ARROW_RIGHT : 8, ARROW_ANY : 15 + }, + Direction:{ + BACK: 0, + FRONT: 1 } }; @@ -2347,7 +2385,7 @@ function initRead(reader, file) { if (typeof file == 'string') { // Deprecated in Cordova 2.4. - console.warning('Using a string argument with FileReader.readAs functions is deprecated.'); + console.warn('Using a string argument with FileReader.readAs functions is deprecated.'); reader._fileName = file; } else if (typeof file.fullPath == 'string') { reader._fileName = file.fullPath; @@ -2398,14 +2436,7 @@ FileReader.prototype.readAsText = function(file, encoding) { // Default encoding is UTF-8 var enc = encoding ? encoding : "UTF-8"; var me = this; - var execArgs = [this._fileName, enc]; - - // Maybe add slice parameters. - if (file.end < file.size) { - execArgs.push(file.start, file.end); - } else if (file.start > 0) { - execArgs.push(file.start); - } + var execArgs = [this._fileName, enc, file.start, file.end]; // Read file exec( @@ -2474,14 +2505,7 @@ FileReader.prototype.readAsDataURL = function(file) { } var me = this; - var execArgs = [this._fileName]; - - // Maybe add slice parameters. - if (file.end < file.size) { - execArgs.push(file.start, file.end); - } else if (file.start > 0) { - execArgs.push(file.start); - } + var execArgs = [this._fileName, file.start, file.end]; // Read file exec( @@ -2544,9 +2568,59 @@ FileReader.prototype.readAsBinaryString = function(file) { if (initRead(this, file)) { return this._realReader.readAsBinaryString(file); } - // TODO - Can't return binary data to browser. - console.log('method "readAsBinaryString" is not supported at this time.'); - this.abort(); + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsBinaryString", execArgs); }; /** @@ -2558,9 +2632,59 @@ FileReader.prototype.readAsArrayBuffer = function(file) { if (initRead(this, file)) { return this._realReader.readAsArrayBuffer(file); } - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); - this.abort(); + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsArrayBuffer", execArgs); }; module.exports = FileReader; @@ -2606,6 +2730,38 @@ function newProgressEvent(result) { return pe; } +function getBasicAuthHeader(urlString) { + var header = null; + + if (window.btoa) { + // parse the url using the Location object + var url = document.createElement('a'); + url.href = urlString; + + var credentials = null; + var protocol = url.protocol + "//"; + var origin = protocol + url.host; + + // check whether there are the username:password credentials in the url + if (url.href.indexOf(origin) !== 0) { // credentials found + var atIndex = url.href.indexOf("@"); + credentials = url.href.substring(protocol.length, atIndex); + } + + if (credentials) { + var authHeader = "Authorization"; + var authHeaderValue = "Basic " + window.btoa(credentials); + + header = { + name : authHeader, + value : authHeaderValue + }; + } + } + + return header; +} + var idCounter = 0; /** @@ -2636,11 +2792,25 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro var params = null; var chunkedMode = true; var headers = null; + var httpMethod = null; + var basicAuthHeader = getBasicAuthHeader(server); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + if (options) { fileKey = options.fileKey; fileName = options.fileName; mimeType = options.mimeType; headers = options.headers; + httpMethod = options.httpMethod || "POST"; + if (httpMethod.toUpperCase() == "PUT"){ + httpMethod = "PUT"; + } else { + httpMethod = "POST"; + } if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { chunkedMode = options.chunkedMode; } @@ -2653,7 +2823,7 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro } var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); errorCallback(error); }; @@ -2667,7 +2837,7 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro successCallback && successCallback(result); } }; - exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); }; /** @@ -2677,10 +2847,24 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro * @param successCallback (Function} Callback to be invoked when upload has completed * @param errorCallback {Function} Callback to be invoked upon error * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + * @param options {FileDownloadOptions} Optional parameters such as headers */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); var self = this; + + var basicAuthHeader = getBasicAuthHeader(source); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + var headers = null; + if (options) { + headers = options.headers || null; + } + var win = function(result) { if (typeof result.lengthComputable != "undefined") { if (self.onprogress) { @@ -2703,20 +2887,19 @@ FileTransfer.prototype.download = function(source, target, successCallback, erro }; var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); errorCallback(error); }; - exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id]); + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id, headers]); }; /** - * Aborts the ongoing file transfer on this object - * @param successCallback {Function} Callback to be invoked upon success - * @param errorCallback {Function} Callback to be invoked upon error + * Aborts the ongoing file transfer on this object. The original error + * callback for the file transfer will be called if necessary. */ -FileTransfer.prototype.abort = function(successCallback, errorCallback) { - exec(successCallback, errorCallback, 'FileTransfer', 'abort', [this._id]); +FileTransfer.prototype.abort = function() { + exec(null, null, 'FileTransfer', 'abort', [this._id]); }; module.exports = FileTransfer; @@ -2760,12 +2943,13 @@ define("cordova/plugin/FileUploadOptions", function(require, exports, module) { * @param headers {Object} Keys are header names, values are header values. Multiple * headers of the same name are not supported. */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers) { +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { this.fileKey = fileKey || null; this.fileName = fileName || null; this.mimeType = mimeType || null; this.params = params || null; this.headers = headers || null; + this.httpMethod = httpMethod || null; }; module.exports = FileUploadOptions; @@ -3100,11 +3284,13 @@ define("cordova/plugin/InAppBrowser", function(require, exports, module) { var exec = require('cordova/exec'); var channel = require('cordova/channel'); +var modulemapper = require('cordova/modulemapper'); function InAppBrowser() { this.channels = { 'loadstart': channel.create('loadstart'), 'loadstop' : channel.create('loadstop'), + 'loaderror' : channel.create('loaderror'), 'exit' : channel.create('exit') }; } @@ -3127,6 +3313,26 @@ InAppBrowser.prototype = { if (eventname in this.channels) { this.channels[eventname].unsubscribe(f); } + }, + + executeScript: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]); + } else { + throw new Error('executeScript requires exactly one of code or file to be specified'); + } + }, + + insertCSS: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]); + } else { + throw new Error('insertCSS requires exactly one of code or file to be specified'); + } } }; @@ -3135,7 +3341,14 @@ module.exports = function(strUrl, strWindowName, strWindowFeatures) { var cb = function(eventname) { iab._eventHandler(eventname); }; - exec(cb, null, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); + + // Don't catch calls that write to existing frames (e.g. named iframes). + if (window.frames && window.frames[strWindowName]) { + var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); + return origOpenFunc.apply(window, arguments); + } + + exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); return iab; }; @@ -4101,7 +4314,7 @@ console.debug = function() { console.assert = function(expression) { if (expression) return; - var message = utils.vformat(arguments[1], [].slice.call(arguments, 2)); + var message = logger.format.apply(logger.format, [].slice.call(arguments, 1)); console.log("ASSERT: " + message); }; @@ -4341,7 +4554,8 @@ modulemapper.clobbers('cordova/plugin/device', 'device'); // file: lib/common/plugin/echo.js define("cordova/plugin/echo", function(require, exports, module) { -var exec = require('cordova/exec'); +var exec = require('cordova/exec'), + utils = require('cordova/utils'); /** * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. @@ -4351,11 +4565,25 @@ var exec = require('cordova/exec'); * @param forceAsync Whether to force an async return value (for testing native->js bridge). */ module.exports = function(successCallback, errorCallback, message, forceAsync) { - var action = forceAsync ? 'echoAsync' : 'echo'; - if (!forceAsync && message.constructor == ArrayBuffer) { - action = 'echoArrayBuffer'; + var action = 'echo'; + var messageIsMultipart = (utils.typeName(message) == "Array"); + var args = messageIsMultipart ? message : [message]; + + if (utils.typeName(message) == 'ArrayBuffer') { + if (forceAsync) { + console.warn('Cannot echo ArrayBuffer with forced async, falling back to sync.'); + } + action += 'ArrayBuffer'; + } else if (messageIsMultipart) { + if (forceAsync) { + console.warn('Cannot echo MultiPart Array with forced async, falling back to sync.'); + } + action += 'MultiPart'; + } else if (forceAsync) { + action += 'Async'; } - exec(successCallback, errorCallback, "Echo", action, [message]); + + exec(successCallback, errorCallback, "Echo", action, args); }; @@ -5068,83 +5296,6 @@ module.exports = { }); -// file: lib/ios/plugin/ios/console.js -define("cordova/plugin/ios/console", function(require, exports, module) { - -var exec = require('cordova/exec'); - -/** - * create a nice string for an object - */ -function stringify(message) { - try { - if (typeof message === "object" && JSON && JSON.stringify) { - try { - return JSON.stringify(message); - } - catch (e) { - return "error JSON.stringify()ing argument: " + e; - } - } else { - return message.toString(); - } - } catch (e) { - return e.toString(); - } -} - -/** - * Wrapper one of the console logging methods, so that - * the Cordova logging native is called, then the original. - */ -function wrappedMethod(console, method) { - var origMethod = console[method]; - - return function(message) { - exec(null, null, - 'Debug Console', 'log', - [ stringify(message), { logLevel: method.toUpperCase() } ] - ); - - if (!origMethod) return; - - origMethod.apply(console, arguments); - }; -} - -var console = window.console || {}; - -// 2012-10-06 pmuellr - marking setLevel() method and logLevel property -// on console as deprecated; -// it didn't do anything useful, since the level constants weren't accessible -// to anyone - -console.setLevel = function() {}; -console.logLevel = 0; - -// wrapper the logging messages - -var methods = ["log", "debug", "info", "warn", "error"]; - -for (var i=0; i tag. + function injectScript(path) { + scriptCounter++; + var script = document.createElement("script"); + script.onload = scriptLoadedCallback; + script.src = path; + document.head.appendChild(script); + } + + // Called when: + // * There are plugins defined and all plugins are finished loading. + // * There are no plugins to load. + function finishPluginLoading() { + context.cordova.require('cordova/channel').onPluginsReady.fire(); + } + + // Handler for the cordova_plugins.json content. + // See plugman's plugin_loader.js for the details of this object. + // This function is only called if the really is a plugins array that isn't empty. + // Otherwise the XHR response handler will just call finishPluginLoading(). + function handlePluginsObject(modules) { + // First create the callback for when all plugins are loaded. + var mapper = context.cordova.require('cordova/modulemapper'); + onScriptLoadingComplete = function() { + // Loop through all the plugins and then through their clobbers and merges. + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + if (!module) continue; + + if (module.clobbers && module.clobbers.length) { + for (var j = 0; j < module.clobbers.length; j++) { + mapper.clobbers(module.id, module.clobbers[j]); + } + } + + if (module.merges && module.merges.length) { + for (var k = 0; k < module.merges.length; k++) { + mapper.merges(module.id, module.merges[k]); + } + } + + // Finally, if runs is truthy we want to simply require() the module. + // This can be skipped if it had any merges or clobbers, though, + // since the mapper will already have required the module. + if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) { + context.cordova.require(module.id); + } } - }; - // boot up once native side is ready - channel.onNativeReady.subscribe(_self.boot); + finishPluginLoading(); + }; - // _nativeReady is global variable that the native side can set - // to signify that the native code is ready. It is a global since - // it may be called before any cordova JS is ready. - if (window._nativeReady) { - channel.onNativeReady.fire(); + // Now inject the scripts. + for (var i = 0; i < modules.length; i++) { + injectScript(modules[i].file); + } } + + // Try to XHR the cordova_plugins.json file asynchronously. + try { // we commented we were going to try, so let us actually try and catch + var xhr = new context.XMLHttpRequest(); + xhr.onload = function() { + // If the response is a JSON string which composes an array, call handlePluginsObject. + // If the request fails, or the response is not a JSON array, just call finishPluginLoading. + var obj = this.responseText && JSON.parse(this.responseText); + if (obj && obj instanceof Array && obj.length > 0) { + handlePluginsObject(obj); + } else { + finishPluginLoading(); + } + }; + xhr.onerror = function() { + finishPluginLoading(); + }; + xhr.open('GET', 'cordova_plugins.json', true); // Async + xhr.send(); + } + catch(err){ + finishPluginLoading(); + } }(window)); + })(); \ No newline at end of file diff --git a/www/images/aerobictraining/boys.png b/www/images/aerobictraining/boys.png new file mode 100644 index 0000000..54a1bcc Binary files /dev/null and b/www/images/aerobictraining/boys.png differ diff --git a/www/images/aerobictraining/girls.png b/www/images/aerobictraining/girls.png new file mode 100644 index 0000000..b8bfdae Binary files /dev/null and b/www/images/aerobictraining/girls.png differ diff --git a/www/images/aerobictraining/trainingzones.png b/www/images/aerobictraining/trainingzones.png new file mode 100644 index 0000000..39c57bd Binary files /dev/null and b/www/images/aerobictraining/trainingzones.png differ diff --git a/www/index.html b/www/index.html index 73ff145..e4b8a3f 100644 --- a/www/index.html +++ b/www/index.html @@ -7,9 +7,14 @@ TDA Enrichment App + - + + @@ -56,7 +62,8 @@

Welcome

+ +
@@ -183,7 +194,7 @@

© George Garside

- Back + Back
@@ -278,7 +289,7 @@

© George Garside

- Back + Back
@@ -404,7 +415,7 @@

© George Garside

- Back + Back
@@ -467,7 +478,7 @@

© George Garside

- Back + Back
@@ -545,7 +556,7 @@

© George Garside

- Back + Back
@@ -620,7 +631,7 @@

Timetable

- Back + Back
@@ -633,7 +644,7 @@

Timetable

-

The First Wheel: Aerobic Training

+

1st: Aerobic Training

This is the wheel that those desperate to lose weight fast always drive on first. Even though it's not the answer to helping you obtain that celebrity-perfect look by itself, aerobic exercise is still a valuable wheel.

The Facts

Without aerobic training, you'll lack the adequate stamina to push your muscles beyond what they're capable of doing so they will become stronger and shape up faster. Without aerobic training you'll have to watch calories religiously, your body wont be as efficient at flushing out the toxins that resistance training leaves behind, such as lactic acid. Add the fact that aerobic training improves the quality of your sleep and its pretty easy to see how it fits into the bigger picture.

@@ -651,7 +662,7 @@

Know When Too Much Is Too Much

Pushing yourself higher within your target heart rate zone will definitely reap more results, but you need to know when too much is too much. If you can't speak a full sentence while exercising without being winded, then you're most likely pushing yourself to the far end of your target heart rate zone. On the other hand, if you're able to carry on a full conversation, then you're probably working at a level thats below your target heart rate zone.

-

The Second Wheel: Resistance Training

+

2nd: Resistance Training

Whether you're brand new to this whole exercise experience, or fitness is old hat, you probably still know what resistance training entails, or know it by one of its many other names: strength training, weight training, pumping iron, anaerobic exercise, etc.

This is the chapter you've either been waiting for or dreading. If you're a male and breathing, you may start to get a bit fired up by now. If you're a female, and fairly new to fitness, you might have doubts, or even visions of morphing into a 300-pound monster with arms like ham hocks as soon as you start bench pressing. Relax, its not like that.

The Facts

@@ -687,7 +698,7 @@
Can I burn fat from a specific area by doing specific exercise?

Until you reduce the body fat above or around your muscles through cardiovascular exercise, resistance training, eating right, and adequate rest you'll never make those muscles or perfect set of abs visible.

-

The Third Wheel: Nutrition

+

3rd: Nutrition

Eating right isn't really all that complicated. It's a choice, and a conscious, daily choice at that. Making the right choice on a regular basis, and sticking with your choice over time, will get you there.

The Facts

Choose to eat the right way and you won't just improve every possible aspect of your health, but you'll make the job of reshaping yourself through exercising that much easier. The time-starved amongst us are at the mercy of convenience eating. You are your own food-police deciding the type of foods you eat; I can't monitor that all the time because I'm only with you for 1 hour at the gym. The other 23 hours are your responsibility.

@@ -731,7 +742,7 @@

Carbohydrates, Proteins, and fats

-

The Fourth Wheel: Rest and Recovery

+

4th: Rest and Recovery

It sounds like the easiest wheel of the five to drive on, but it's actually the one that most frequently gets either too much or too little use. Give your body too much rest and it'll look like it. Give it too little and you increase your odds of not only injuring yourself, but never seeing the body-shaping results you're shooting for.

The facts

Whether you hate or love exercise, your body interprets all that time you've spent pulling, pushing, and sweating on gym equipment as on thing: stress. Thats why the time you give your muscles to rest and recover in between each workout can make a huge difference in the kind of results you end up seeing for all of your hard work.

@@ -740,7 +751,7 @@

The facts

A decent nights sleep. Most healthy adults need an average of 7 to 9 hours of sleep a night. Whatever your cycle is, find it, stick with it.

-

The Steering Wheel: Mental Conditioning

+

5th: Mental Conditioning

What makes one person lie on a sofa eating pizza, chips and cake, while another may be up at 6am pounding the streets trying to beat a personal best? Why are some people more motivated than others? Not just at sport, exercise or at work, but in life?

Each of us has motivations, goals and inspirational figures that shape what we do, what we think, how we react. Some of these goals are verbalised, debated and shared, some are deep rooted and personal. It all starts in the mind.

Life is a mind game. All your aspirations are played out in your head. All your relationships are shaped in your head. Your mind dictates whether you fulfil your potential or live your life dreaming. It's about attitude. The good thing in life is that you can choose your attitude, you cant always choose or change the circumstances in your arena, but you can always choose or change your attitude.

@@ -754,7 +765,7 @@

The Steering Wheel: Mental Conditioning

- Back + Back
@@ -825,7 +836,7 @@

What are the benefits of a cool down?

- Back + Back
@@ -854,34 +865,52 @@

The Facts

Training Frequency

Training Age

Muscle Group Training

-
- Legs - Back - Chest - Shoulders - Arms +
+ Legs + Arms +
+ +

Resources

+ + + +
+
+

Are you sure you want to open this resource?

+

This action can use data, and may not work on some Android devices.

+
+ + +
+
+
- Back + Back
@@ -993,7 +1022,7 @@

Workout 2 – Legs, Back, Biceps

  • Thur: rest or cardio
  • Fri: Workout 2
  • - Workout 1 – Chest, Shoulders, Triceps +

    Workout 1 – Chest, Shoulders, Triceps

    Warm up first, then warm up specific muscle group with a couple of lighter sets, then go into the workout....

    CHEST

    Flat Dumbbell bench press 3 sets (15,12,10)

    @@ -1008,7 +1037,7 @@

    Workout 2 – Legs, Back, Biceps

    TRICEPS

    Lying Triceps extension 3 sets (15,12,10)

    Triceps press down 3 sets (12,12,10)

    - Workout 2 – Legs, Back, Biceps +

    Workout 2 – Legs, Back, Biceps

    Warm up first, then warm up specific muscle group with a couple of lighter sets, then go into the workout....

    LEGS

    Squat 3 sets (10, 10, 10)

    @@ -1249,6 +1278,32 @@

    Basic Arm Routine

    + +
    + +

    Super Seven Strength

    + +
    + +

    It’s absolutely essential you warm up first, 5/10 mins of light CV, then low weight reps until thoroughly warmed up.

    +
      +
    1. Squats - 4 sets (heavy one week, moderate the next). 15/12/12/10 reps (moderate week, bodyweight x 20 reps, 3 sets.)
    2. +
    3. Lunges - 3 sets. 8/8/8 reps on each leg.
    4. +
    5. Deadlifts - 3 sets. 15/12/10 reps
    6. +
    7. Pullups - 2 sets, to failure. Aim for 10, then 5.
    8. +
    9. Bent over Rows - 2 sets. 12/10.
    10. +
    11. Bench Press - 3 sets. 12/10/8
    12. +
    13. Core - 3 sets of, swiss-ball roll-outs x 12/plank 45secs/supermans x 10
    14. +
    +

    If you can make the reps easily then increase the weight. The last couple of reps should be difficult/almost failure. Try to do this at least twice a week (Mon & Fri). Record what you do.

    +

    Forget the beach exercises (arm training!) until you've completed the above routine. Biceps and Triceps are already being used doing the above moves.

    +

    Back

    + +
    + +
    + +
    @@ -1289,14 +1344,116 @@

    How often to train?

    It is important to start slowly. If you have been inactive for some time, don’t throw on your trainers and set off on a ten mile run.

    If a half an hour session feels like too much, start with 10 minute sessions for the first week then increase that to 15 or 20 minute sessions the next week and so on until you feel comfortable exercising for longer.

    Make sure that you listen to your body. It’s ok for muscles to be tired, but if they or your joints start to feel sore or breathing becomes uncomfortable slow down or stop to ensure that you are in a fit state for your next session.

    + +

    Resources

    + + + +
    +
    +

    Are you sure you want to open this resource?

    +

    This action can use data, and may not work on some Android devices.

    +
    + + +
    +
    +
    +
    +
    +

    Are you sure you want to open this resource?

    +

    This action can use data, and may not work on some Android devices.

    +
    + + +
    +
    +
    +
    +
    +

    Disabled

    + +

    This external resource has been removed since it no longer exists. This button, along with this message, will be completely removed in a future update. Sorry for the inconvenience.

    + +
    + + +
    +
    +
    - Back + Back
    + +
    + +

    Training Zones

    + +
    + Training Zones +
    + Close + Training Zones + +
    + +

    The Energy Efficient zone 60-70%: Training within this zone develops basic endurance and aerobic capacity. All easy recovery running should be completed at a maximum of 70%. Another advantage to running in this zone is that while you are happily fat burning you may lose weight and you will be allowing your muscles to re-energise with glycogen, which has been expended during those faster paced workouts.

    +

    The Aerobic zone 70-80%: Training in this zone will develop your cardiovascular system. The body's ability to transport oxygen to, and carbon dioxide away from, the working muscles can be developed and improved. As you become fitter and stronger from training in this zone it will be possible to run some of your long weekend runs at up to 75%, so getting the benefits of some fat burning and improved aerobic capacity.

    +

    The Anaerobic Zone 80-90%: Training in this zone will develop your lactic acid system. In this zone, your individual anaerobic threshold (AT) is found - sometimes referred to the point of deflection (POD). During these heart rates, the amount of fat being utilised as the main source of energy is greatly reduced and glycogen stored in the muscle is predominantly used. One of the by-products of burning this glycogen is lactic acid. There is a point at which the body can no longer remove the lactic acid from the working muscles quickly enough. This is your anaerobic threshold (AT). Through the correct training, it is possible to delay the AT by being able to increase your ability to deal with the lactic acid for a longer period of time or by pushing the AT higher.

    +

    The Red-line Zone 90-100%: Training in this zone will only be possible for short periods. It effectively trains your fast twitch muscle fibres and helps to develop speed. This zone is reserved for interval running and only the very fit are able to train effectively within this zone.

    + +

    Back

    + +
    + +
    + + +
    + +

    Fitness Testing

    + +
    +

    This test is designed to measure your cardiovascular endurance

    +

    Using a 12 inch high bench (or a similar sized stair in your house), step on and off for 3 minutes.

    +

    Step up with one foot and then the other. Step down with one foot followed by the other foot.

    +

    Try to maintain a steady four beat cycle. It's easy to maintain if you say "up, up, down, down".

    +

    Go at a steady and consistent pace.

    +

    At the end of 3 minutes, remain standing and immediately check your heart rate by taking your pulse…

    + + Fitness Testing Boys Table +
    + Close + Fitness Testing Boys Table + +
    + + Fitness Testing Girls Table +
    + Close + Fitness Testing Girls Table + +
    + +

    The Aerobic zone 70-80%: Training in this zone will develop your cardiovascular system. The body's ability to transport oxygen to, and carbon dioxide away from, the working muscles can be developed and improved. As you become fitter and stronger from training in this zone it will be possible to run some of your long weekend runs at up to 75%, so getting the benefits of some fat burning and improved aerobic capacity.

    +

    The Anaerobic Zone 80-90%: Training in this zone will develop your lactic acid system. In this zone, your individual anaerobic threshold (AT) is found - sometimes referred to the point of deflection (POD). During these heart rates, the amount of fat being utilised as the main source of energy is greatly reduced and glycogen stored in the muscle is predominantly used. One of the by-products of burning this glycogen is lactic acid. There is a point at which the body can no longer remove the lactic acid from the working muscles quickly enough. This is your anaerobic threshold (AT). Through the correct training, it is possible to delay the AT by being able to increase your ability to deal with the lactic acid for a longer period of time or by pushing the AT higher.

    +

    The Red-line Zone 90-100%: Training in this zone will only be possible for short periods. It effectively trains your fast twitch muscle fibres and helps to develop speed. This zone is reserved for interval running and only the very fit are able to train effectively within this zone.

    + +

    Back

    + +
    + +
    +
    @@ -1338,19 +1495,24 @@

    Introduction

  • Don’t skip breakfast
  • - Food Labelling Information - +

    Resources

    + + +
    - Back + Back
    -

    H&F Nutrition Food Labelling

    +

    Nutrition Food Labelling

    @@ -1408,14 +1570,14 @@

    Guideline Daily Amounts

    window.onload = function() { var gdaimgsource = document.getElementById("gdaimgsource"); gdaimgsource.onclick = function() { - cordova.exec("ChildBrowserCommand.showWebPage", "http://www.danone.co.uk/SiteImages/Assets/0/GDA_545px.jpg" ); + window.open('http://www.danone.co.uk/SiteImages/Assets/0/GDA_545px.jpg', '_blank', 'location=yes'); return false; } }

    An example of a GDA. Source

    Guideline Daily Amounts (GDAs) were developed by food manufacturers and retailers and you can find out more by visiting the IGD website. IGD is a food and grocery industry research organisation.

    @@ -1426,10 +1588,95 @@

    Ingredients list

    - Back + Back
    + +
    + +

    Nutrition Classification

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CarbohydratesEnergy can be stored in the body in the form of carbohydrates and this is particularly important for endurance performers, who may not be able to restock their energy stores adequately while they are competing. Carbohydrates are high in glucose and enter the blood. Excess glucose is stored as glycogen in the liver and muscles. Extra glucose is not wasted, but converted into glycogen or fat and stored.Cereals, All wholegrain foods, Potato, Rice, Vegetables, Fruits, Beans, Pasta.
    ProteinsIt contributes to the development and growth of hormones and haemoglobin in the blood, both of which are essential for those involved in sport or regular physical activity. Protein is necessary for the growth and repair of body tissues. It is also a relatively minor source of energy (10%).Meat, Fish, Eggs, Milk, Cheese, Cereals, Nuts.
    FatsThere are many forms of fat in the body and it supplies around 70% of our energy requirements. Fat supplies energy (calories) that can either be used immediately or stored for future use. Although fat is the body’s preferred energy source, it also uses energy in the form of stored carbohydrate (glycogen) for intensive burst of energy. If we consume an excessive amount of fat, it is stored in the body tissues and causes us to become overweight. In order to avoid this, dieticians recommend that we should not consume more than ⅓rd of our daily nutritional needs in the form of fat.

    Saturated – meat, burgers, sausages, butter, cheese.

    +

    Hydrogenated – margarine, biscuits, cakes, puddings, chocolate.

    +

    Polyunsaturated / Omega 6 – vegetable oils, nuts, seeds

    +

    Omega 3 – Oily fish, walnuts, soya beans, sweet potatoes

    +

    Monounsaturated – Olive oil, olives, avocados, nuts, seeds, rapeseed oil

    FibreDietary fibre comprises the edible parts of plants that are not broken down and absorbed in the small intestine. It does not contain any nutrients. It therefore adds bulk to our food without adding extra kilojoules. Fibre regulates the digestive system.Whole-grain and high-fibre breakfast cereals and bread, fruit and vegetables.
    MineralsMinerals are required by the body for a variety of functions including the formation of bones & teeth, as essential constituents of body fluids & tissues, components of enzyme systems, and nerve function.

    Calcium for bones and teeth – milk, cheese, dairy products.

    +

    Magnesium for energy metabolism – dark green leafy vegetables, grains and nuts.

    +

    Sodium for regulating body fluids - salted foods, crisps, canned foods.

    +

    Potassium for water and electrolyte balance – dried fruits, bananas, berries, veg, milk.

    +

    Iron for formation of haemoglobin/oxygen transport – spinach, dark green veg, liver, red meat, nuts, peas

    +

    Iodine for hormone formation – salt water fish, milk.

    Vitamins

    Vitamins are chemical compounds found in the food we eat. Our bodies cannot make vitamins so must take them from our food. Vitamins perform the following functions: aiding growth, increasing resistance to infection, regulating some body functions such as good vision, and helping the metabolism of certain foods.

    +

    There are two main groups of Vitamins:

    +
      +
    1. FAT Soluble Vitamins – Vitamins A, D, & K, they can be stored in our body.
    2. +
    3. WATER Soluble Vitamins – Vitamins C & B, They cannot be stored in the body.
    4. +

    Vitamin A – Liver, whole milk, cheese, butter, carrots, leafy veg.

    +

    Vitamin D – Oily fish, eggs, butter, and meat.

    +

    Vitamin K – most plant and animal sources, also made by bacteria in the gut.

    +

    Vitamin C – plant sources, fresh milk, liver.

    +

    Vitamin B1 – Whole grains, nuts, meat.

    +

    Vitamin B2 – milk, eggs, breakfast cereals, liver, green veg.

    +

    Niacin – meat.

    +

    Vitamin B6 – beef, fish, poultry, eggs, whole grains, some veg.

    +

    Vitamin B12 – milk, meat, eggs.

    WaterWater makes up 70% of the human body. It is essential for the correct functioning of virtually all living cells. Water has many functions: it provides the medium in which most reactions in the body occur, it acts as a lubricant for joints and eyes, and it helps regulate body temperature. The amount of water we drink varies from person to person, depending on age, time of year, climatic conditions, diet, the amount of physical activity we do. Dehydration is common amongst athletes, especially in hot climates, or at altitude. As little as a 2% loss of bodyweight can result in impaired responses and performance. Water replacement is essential before, during and after exercise.

    Tap water is suitable for replacing lost fluid following mild or moderate exercise.

    +

    Isotonic drinks are more suitable after vigorous or prolonged exercise.

    + +
    + +
    + Back +
    +
    +
    @@ -1474,7 +1721,7 @@

    The basics

    - Back + Back
    @@ -1539,7 +1786,7 @@

    The Press-up

    - Back + Back
    @@ -1573,7 +1820,7 @@

    Dynamic Floor Exercises

    - Back + Back
    @@ -1589,6 +1836,10 @@

    I want to burn calories quickly…

    Answer: Interval training. It doesn’t matter what you do, run, row, or jog on the spot. If you do these exercises at FULL pace (after a warm up) for 20 seconds, then have a 40 second rest, repeating this for up to 5 minutes, it will burn a huge amount of calories. The good thing about this type of training is that your metabolism is given a jump-start so it keeps burning calories for hours after you have finished.

    I want a fast fitness workout…

    Answer: Crossfit training. This type of training focuses on strength and endurance training and is scalable to your own fitness and ability level. It also burns lots of calories. For example, a favourite of mine is to do 5 pull-ups, 10 push-ups then 15 bodyweight squats, one after the other, no rest. Each time you complete this its one rep. The idea is that you do as many reps as you can in 10 minutes. If you can’t do the pull-ups then swap them for something like bench-dips or anything you can do.

    +
    +

    Here are a few crossfit routines:

    + +

    How do I lose weight?

    Answer: Eat sensibly, increase your activity. We all know when the food we eat is a ‘treat’ or something that’s ‘clean’. The difference between the two is our own motivation. If we have a goal or a beach holiday coming up its much easier to be strict with what we eat. The way I look at it is this; its MUCH easier to stay in shape than to GET in shape. Maintenance is the key to a healthy weight, once you get there. Fast answer; walk briskly when you walk, jog up the stairs, bike instead of getting a lift, say no to the second helping, and yes just once a week to treats… you get the picture.

    So try crossfit and interval training as faster ways to increase your activity. If this is too intense then start with 10 minutes on the cross-trainer or bike. Get into the habit of doing this and you will see a difference in your health.

    @@ -1600,26 +1851,244 @@

    How do I tone my legs?

    - Back + Back
    - -
    + +
    -

    blank

    +

    Crossfit Routines

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -
    +
    +
    Angie +
      +
    • 100 Pull-ups
    • +
    • 100 Push-ups
    • +
    • 100 Sit-ups
    • +
    • 100 Squats
    • +
    +
    For Time. Complete all reps of each exercise before moving to the next.
    Barbara +
      +
    • 20 Pull-ups
    • +
    • 30 Push-ups
    • +
    • 40 Sit-ups
    • +
    • 50 Squats
    • +
    +

    Rest precisely three minutes between each round.

    +
    5 rounds, time each round.
    Chelsea +
      +
    • 5 Pull-ups
    • +
    • 10 Push-ups
    • +
    • 15 Squats
    • +
    +
    Each min on the min for 30 min.
    Cindy +
      +
    • 5 Pull-ups
    • +
    • 10 Push-ups
    • +
    • 15 Squats
    • +
    +
    As many rounds as possible in 20 min.
    Fran +
      +
    • Thruster 95 lbs
    • +
    • Pull-ups
    • +
    +
    21-15-9 reps, for time.
    Karen +
      +
    • Wall-ball 150 shots
    • +
    +
    For time.
    Nancy +
      +
    • 400 meter run
    • +
    • Overhead squat 95 lbs x 15
    • +
    +
    5 rounds for time.
    Kelly +
      +
    • Run 400 meters
    • +
    • 30 box jump, 24 inch box
    • +
    • 30 Wall ball shots, 20 pound ball
    • +
    +
    Five rounds for time.
    Nicole +
      +
    • Run 400 meters
    • +
    • Max rep Pull-ups
    • +
    +
    As many rounds as possible in 20 minutes.
    TDA 5 +

    Five rounds of:

    +
      +
    • 30 push-ups
    • +
    • 40 sit-ups
    • +
    • 50 Air Squats
    • +
    +
    For time.
    Crossfit 25 +
      +
    • Sprint 100M
    • +
    • 25 push-ups
    • +
    • Sprint 100M
    • +
    • 25 crunches
    • +
    • Sprint 100M
    • +
    • 25 air Squats
    • +
    • Sprint 100
    • +
    • 25 burpees
    • +
    +
    Three rounds for time.
    RSP +
      +
    • Run ½ mile (1 lap of back field)
    • +
    • 100 Air Squats
    • +
    • 50 push-ups
    • +
    • Run ½ mile
    • +
    +
    For time.
    Filthy Fifty +
      +
    • 50 push-up
    • +
    • 50 squats
    • +
    • 50 crunches
    • +
    • 50 lunges
    • +
    • 50 burpees
    • +
    +
    For time.
    Ten-minute Challenge +

    One of the following:

    +
      +
    • Max push-ups
    • +
    • Max Pull-ups
    • +
    • Air Squats
    • +
    • Burpees
    • +
    +
    Record score.
    10-10-10 +

    10 push-ups, 10-crunches, 10 air squats – 10 rounds of.

    +

    Set 10 cones, 10M apart, do 1st round at start cone then sprint to the next cone. Repeat. Sprint back from last cone.

    +
    Burpees for time +
      +
    • 100 burpees
    • +
    +
    For time.
    + +

    Back

    + +
    - -

    +

    -

    + +
    + +

    H&F Info

    +
    + + +

    Welcome

    + +

    Welcome to the TDA Health & Fitness mini-site. Here you will find information on the main aspects of Health and Fitness along with other resources. If you can't find what you want on here, or require further information, please e-mail Mr Hoffmann with suggestions or ideas.

    +

    Before you start on ANY fitness training program, remember: if you have any injuries, medical conditions or are not sure about what to do, ask a member of staff or check with your doctor that it's fine for you to train.

    +

    Warm-ups. You HAVE to do them, read the WARM-UP page first and INCLUDE one with every workout, run, row, team game, individual event. Make it a HABIT and it will go along way to help you stay injury-free.

    + +

    Acknowledgements

    + +
      +
    • ‘G-Force’, the ultimate guide to you best body ever – Gunnar Peterson
    • +
    • ‘ABSolution’ – Shawn Phillips
    • +
    • ‘Mind Games’ – Jeff Grout & Sarah Perrin
    • +
    +
    +

    Some information also provided by Mr Hoffmann.

    +
    - Back + Back