aboutsummaryrefslogtreecommitdiffstats
path: root/iPhone/CordovaLib/Classes
diff options
context:
space:
mode:
Diffstat (limited to 'iPhone/CordovaLib/Classes')
-rwxr-xr-xiPhone/CordovaLib/Classes/CDV.h2
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVAvailability.h22
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCamera.h14
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCamera.m194
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCapture.m20
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandDelegate.h1
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m22
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandQueue.h8
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandQueue.m17
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConfigParser.h5
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConfigParser.m28
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConnection.m12
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContact.m32
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContacts.h14
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContacts.m490
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDebugConsole.m37
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDevice.m1
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVEcho.m7
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVExif.h43
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFile.h5
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFile.m318
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFileTransfer.h16
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFileTransfer.m278
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVGlobalization.m62
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInAppBrowser.h26
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInAppBrowser.m370
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h6
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h62
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m547
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocalStorage.h4
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocalStorage.m36
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocation.h8
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocation.m24
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVNotification.h1
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVNotification.m93
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPlugin.h8
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPlugin.m9
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPluginResult.h7
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPluginResult.m57
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVReachability.m28
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSound.m24
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSplashScreen.h3
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSplashScreen.m186
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVTimer.h (renamed from iPhone/CordovaLib/Classes/CDVDebugConsole.h)7
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVTimer.m123
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVURLProtocol.m12
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVUserAgentUtil.m2
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVViewController.h10
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVViewController.m172
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWebViewDelegate.h2
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWebViewDelegate.m315
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWhitelist.h6
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWhitelist.m112
-rwxr-xr-xiPhone/CordovaLib/Classes/NSData+Base64.m19
-rwxr-xr-xiPhone/CordovaLib/Classes/NSDictionary+Extensions.m2
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h30
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h46
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m29
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h32
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h23
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/README.txt23
61 files changed, 2923 insertions, 1189 deletions
diff --git a/iPhone/CordovaLib/Classes/CDV.h b/iPhone/CordovaLib/Classes/CDV.h
index 5a0ae6a..15d9316 100755
--- a/iPhone/CordovaLib/Classes/CDV.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVAvailability.h b/iPhone/CordovaLib/Classes/CDVAvailability.h
index 33c6799..7e8157c 100755
--- a/iPhone/CordovaLib/Classes/CDVAvailability.h
+++ b/iPhone/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,10 @@
#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_2_8_0 20800
+#define __CORDOVA_2_9_0 20900
#define __CORDOVA_NA 99999 /* not available */
/*
@@ -47,7 +53,7 @@
#endif
*/
#ifndef CORDOVA_VERSION_MIN_REQUIRED
- #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_5_0
+ #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_9_0
#endif
/*
@@ -65,12 +71,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/iPhone/CordovaLib/Classes/CDVCamera.h b/iPhone/CordovaLib/Classes/CDVCamera.h
index 204d25f..2932e3b 100755
--- a/iPhone/CordovaLib/Classes/CDVCamera.h
+++ b/iPhone/CordovaLib/Classes/CDVCamera.h
@@ -18,6 +18,8 @@
*/
#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+#import <CoreLocation/CLLocationManager.h>
#import "CDVPlugin.h"
enum CDVDestinationType {
@@ -61,11 +63,15 @@ typedef NSUInteger CDVMediaType;
// ======================================================================= //
@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
- UINavigationControllerDelegate,
- UIPopoverControllerDelegate>
+ UINavigationControllerDelegate,
+ UIPopoverControllerDelegate,
+ CLLocationManagerDelegate>
{}
@property (strong) CDVCameraPicker* pickerController;
+@property (strong) NSMutableDictionary *metadata;
+@property (strong, nonatomic) CLLocationManager *locationManager;
+@property (strong) NSData* data;
/*
* getPicture
@@ -85,8 +91,12 @@ typedef NSUInteger CDVMediaType;
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
+- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize;
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize;
- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage;
+- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
+- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
+
@end
diff --git a/iPhone/CordovaLib/Classes/CDVCamera.m b/iPhone/CordovaLib/Classes/CDVCamera.m
index aabe844..d0a36dd 100755
--- a/iPhone/CordovaLib/Classes/CDVCamera.m
+++ b/iPhone/CordovaLib/Classes/CDVCamera.m
@@ -18,9 +18,15 @@
*/
#import "CDVCamera.h"
+#import "CDVJpegHeaderWriter.h"
#import "NSArray+Comparisons.h"
#import "NSData+Base64.h"
#import "NSDictionary+Extensions.h"
+#import <ImageIO/CGImageProperties.h>
+#import <AssetsLibrary/ALAssetRepresentation.h>
+#import <ImageIO/CGImageSource.h>
+#import <ImageIO/CGImageProperties.h>
+#import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h>
#define CDV_PHOTO_PREFIX @"cdv_photo_"
@@ -40,7 +46,7 @@ static NSSet* org_apache_cordova_validArrowDirections;
org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
}
-@synthesize hasPendingOperation, pickerController;
+@synthesize hasPendingOperation, pickerController, locationManager;
- (BOOL)popoverSupported
{
@@ -60,6 +66,8 @@ static NSSet* org_apache_cordova_validArrowDirections;
* 7 allowsEdit
* 8 correctOrientation
* 9 saveToPhotoAlbum
+ * 10 popoverOptions
+ * 11 cameraDirection
*/
- (void)takePicture:(CDVInvokedUrlCommand*)command
{
@@ -122,18 +130,22 @@ static NSSet* org_apache_cordova_validArrowDirections;
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];
@@ -180,6 +192,17 @@ static NSSet* org_apache_cordova_validArrowDirections;
animated:YES];
}
+- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
+{
+ if([navigationController isKindOfClass:[UIImagePickerController class]]){
+ UIImagePickerController * cameraPicker = (UIImagePickerController*)navigationController;
+
+ if(![cameraPicker.mediaTypes containsObject:(NSString*) kUTTypeImage]){
+ [viewController.navigationItem setTitle:NSLocalizedString(@"Videos title", nil)];
+ }
+ }
+}
+
- (void)cleanup:(CDVInvokedUrlCommand*)command
{
// empty the tmp directory
@@ -264,10 +287,6 @@ static NSSet* org_apache_cordova_validArrowDirections;
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
- if (cameraPicker.saveToPhotoAlbum) {
- UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
- }
-
if (cameraPicker.correctOrientation) {
image = [self imageCorrectedForCaptureOrientation:image];
}
@@ -289,12 +308,28 @@ static NSSet* org_apache_cordova_validArrowDirections;
data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage);
} else {
data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f);
+
+ NSDictionary *controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
+ if (controllerMetadata) {
+ self.data = data;
+ self.metadata = [[NSMutableDictionary alloc] init];
+
+ NSMutableDictionary *EXIFDictionary = [[controllerMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];
+ if (EXIFDictionary) [self.metadata setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
+
+ [[self locationManager] startUpdatingLocation];
+ return;
+ }
+ }
+
+ if (cameraPicker.saveToPhotoAlbum) {
+ UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:data], nil, nil, nil);
}
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 +454,7 @@ static NSSet* org_apache_cordova_validArrowDirections;
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 +553,7 @@ static NSSet* org_apache_cordova_validArrowDirections;
// 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
@@ -539,6 +574,143 @@ static NSSet* org_apache_cordova_validArrowDirections;
self.hasPendingOperation = NO;
}
+
+- (CLLocationManager *)locationManager {
+
+ if (locationManager != nil) {
+ return locationManager;
+ }
+
+ locationManager = [[CLLocationManager alloc] init];
+ [locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
+ [locationManager setDelegate:self];
+
+ return locationManager;
+}
+
+- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
+{
+ if (locationManager != nil) {
+ [self.locationManager stopUpdatingLocation];
+ self.locationManager = nil;
+
+ NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
+
+ CLLocationDegrees latitude = newLocation.coordinate.latitude;
+ CLLocationDegrees longitude = newLocation.coordinate.longitude;
+
+ // latitude
+ if (latitude < 0.0) {
+ latitude = latitude * -1.0f;
+ [GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
+ } else {
+ [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
+
+ // longitude
+ if (longitude < 0.0) {
+ longitude = longitude * -1.0f;
+ [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
+ }
+ else {
+ [GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
+
+ // altitude
+ CGFloat altitude = newLocation.altitude;
+ if (!isnan(altitude)){
+ if (altitude < 0) {
+ altitude = -altitude;
+ [GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
+ } else {
+ [GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
+ }
+
+ // Time and date
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ [formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
+ [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
+ [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
+ [formatter setDateFormat:@"yyyy:MM:dd"];
+ [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
+
+ [self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
+ [self imagePickerControllerReturnImageResult];
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
+ if (locationManager != nil) {
+ [self.locationManager stopUpdatingLocation];
+ self.locationManager = nil;
+
+ [self imagePickerControllerReturnImageResult];
+ }
+}
+
+- (void)imagePickerControllerReturnImageResult
+{
+ CDVPluginResult* result = nil;
+
+ if (self.metadata) {
+ CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge_retained CFDataRef)self.data, NULL);
+ CFStringRef sourceType = CGImageSourceGetType(sourceImage);
+
+ CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
+ CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
+ CGImageDestinationFinalize(destinationImage);
+
+ CFRelease(sourceImage);
+ CFRelease(destinationImage);
+ }
+
+ if (self.pickerController.saveToPhotoAlbum) {
+ UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[self data]], nil, nil, nil);
+ }
+
+ if (self.pickerController.returnType == DestinationTypeFileUri) {
+ // write to temp directory and return URI
+ // get the temp directory path
+ 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
+ NSString* filePath;
+
+ int i = 1;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, self.pickerController.encodingType == EncodingTypePNG ? @"png":@"jpg"];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ // save file
+ if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ }
+ else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
+ }
+ }
+ else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]];
+ }
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
+ }
+
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
+ }
+
+ self.hasPendingOperation = NO;
+ self.pickerController = nil;
+ self.data = nil;
+ self.metadata = nil;
+}
+
@end
@implementation CDVCameraPicker
diff --git a/iPhone/CordovaLib/Classes/CDVCapture.m b/iPhone/CordovaLib/Classes/CDVCapture.m
index ed9f664..884702d 100755
--- a/iPhone/CordovaLib/Classes/CDVCapture.m
+++ b/iPhone/CordovaLib/Classes/CDVCapture.m
@@ -108,7 +108,6 @@
if ([options isKindOfClass:[NSNull class]]) {
options = [NSDictionary dictionary];
}
- NSString* mode = [options objectForKey:@"mode"];
// options could contain limit and mode neither of which are supported at this time
// taking more than one picture (limit) is only supported if provide own controls via cameraOverlayView property
@@ -139,7 +138,6 @@
}*/
// CDVImagePicker specific property
pickerController.callbackId = callbackId;
- pickerController.mimeType = mode;
if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
[self.viewController presentViewController:pickerController animated:YES completion:nil];
@@ -169,7 +167,7 @@
}
// 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];
@@ -206,9 +204,9 @@
options = [NSDictionary dictionary];
}
- // options could contain limit, duration and mode, only duration is supported (but is not due to apple bug)
+ // options could contain limit, duration and mode
// taking more than one video (limit) is only supported if provide own controls via cameraOverlayView property
- // NSNumber* duration = [options objectForKey:@"duration"];
+ NSNumber* duration = [options objectForKey:@"duration"];
NSString* mediaType = nil;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
@@ -240,12 +238,12 @@
// iOS 3.0
pickerController.mediaTypes = [NSArray arrayWithObjects:mediaType, nil];
- /*if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]){
+ if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]){
if (duration) {
pickerController.videoMaximumDuration = [duration doubleValue];
}
//NSLog(@"pickerController.videoMaximumDuration = %f", pickerController.videoMaximumDuration);
- }*/
+ }
// iOS 4.0
if ([pickerController respondsToSelector:@selector(cameraCaptureMode)]) {
@@ -439,7 +437,7 @@
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 +531,6 @@
// delegate to CVDAudioRecorderViewController
return [self.topViewController supportedInterfaceOrientations];
}
-
#endif
@end
@@ -663,7 +660,7 @@
// 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 +698,6 @@
orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown);
return orientation;
}
-
#endif
- (void)viewDidUnload
@@ -766,7 +762,7 @@
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/iPhone/CordovaLib/Classes/CDVCommandDelegate.h b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h
index e177c63..0401136 100755
--- a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m
index e399289..fa0e5e0 100755
--- a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m
+++ b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m
@@ -55,7 +55,11 @@
- (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)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 @@
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/iPhone/CordovaLib/Classes/CDVCommandQueue.h b/iPhone/CordovaLib/Classes/CDVCommandQueue.h
index ebdf844..27c47b5 100755
--- a/iPhone/CordovaLib/Classes/CDVCommandQueue.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVCommandQueue.m b/iPhone/CordovaLib/Classes/CDVCommandQueue.m
index a8a58b7..1a0dfa0 100755
--- a/iPhone/CordovaLib/Classes/CDVCommandQueue.m
+++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.m
@@ -23,6 +23,14 @@
#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 @@
@"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 @@
// 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/iPhone/CordovaLib/Classes/CDVConfigParser.h b/iPhone/CordovaLib/Classes/CDVConfigParser.h
index 7392580..2e06c88 100755
--- a/iPhone/CordovaLib/Classes/CDVConfigParser.h
+++ b/iPhone/CordovaLib/Classes/CDVConfigParser.h
@@ -17,7 +17,10 @@
under the License.
*/
-@interface CDVConfigParser : NSObject <NSXMLParserDelegate>{}
+@interface CDVConfigParser : NSObject <NSXMLParserDelegate>
+{
+ NSString* featureName;
+}
@property (nonatomic, readonly, strong) NSMutableDictionary* pluginsDict;
@property (nonatomic, readonly, strong) NSMutableDictionary* settings;
diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.m b/iPhone/CordovaLib/Classes/CDVConfigParser.m
index 6fd5913..55b25e6 100755
--- a/iPhone/CordovaLib/Classes/CDVConfigParser.m
+++ b/iPhone/CordovaLib/Classes/CDVConfigParser.m
@@ -41,6 +41,7 @@
self.settings = [[NSMutableDictionary alloc] initWithCapacity:30];
self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:30];
self.startupPluginNames = [[NSMutableArray alloc] initWithCapacity:8];
+ featureName = nil;
}
return self;
}
@@ -52,9 +53,27 @@
} 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];
}
+ NSLog(@"\nUse of the <plugin> tag has been deprecated. Use a <feature> tag instead. Change:\n"
+ @" <plugin name=\"%@\" value=\"%@\" />\n"
+ @"To:\n"
+ @" <feature name=\"%@\">\n"
+ @" <param name=\"ios-package\" value=\"%@\" />\n"
+ @" </feature>\n"
+ , attributeDict[@"name"], attributeDict[@"value"], attributeDict[@"name"], attributeDict[@"value"]);
+ } else if ([elementName isEqualToString:@"feature"]) { // store feature name to use with correct parameter set
+ featureName = [attributeDict[@"name"] lowercaseString];
+ } else if ((featureName != nil) && [elementName isEqualToString:@"param"]) {
+ NSString* paramName = [attributeDict[@"name"] lowercaseString];
+ id value = attributeDict[@"value"];
+ if ([paramName isEqualToString:@"ios-package"]) {
+ pluginsDict[featureName] = value;
+ }
+ if ([paramName isEqualToString:@"onload"] && [@"true" isEqualToString : value]) {
+ [self.startupPluginNames addObject:featureName];
+ }
} else if ([elementName isEqualToString:@"access"]) {
[whitelistHosts addObject:attributeDict[@"origin"]];
} else if ([elementName isEqualToString:@"content"]) {
@@ -62,6 +81,13 @@
}
}
+- (void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName
+{
+ if ([elementName isEqualToString:@"feature"]) { // no longer handling a feature so release
+ featureName = nil;
+ }
+}
+
- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError
{
NSAssert(NO, @"config.xml parse error line %d col %d", [parser lineNumber], [parser columnNumber]);
diff --git a/iPhone/CordovaLib/Classes/CDVConnection.m b/iPhone/CordovaLib/Classes/CDVConnection.m
index 3030711..b3f5cab 100755
--- a/iPhone/CordovaLib/Classes/CDVConnection.m
+++ b/iPhone/CordovaLib/Classes/CDVConnection.m
@@ -52,7 +52,8 @@
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 @@
{
return [theConnectionType isEqualToString:@"2g"] ||
[theConnectionType isEqualToString:@"3g"] ||
- [theConnectionType isEqualToString:@"4g"];
+ [theConnectionType isEqualToString:@"4g"] ||
+ [theConnectionType isEqualToString:@"cellular"];
}
- (void)updateReachability:(CDVReachability*)reachability
@@ -111,6 +113,7 @@
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 @@
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/iPhone/CordovaLib/Classes/CDVContact.m b/iPhone/CordovaLib/Classes/CDVContact.m
index 9efaf10..3844525 100755
--- a/iPhone/CordovaLib/Classes/CDVContact.m
+++ b/iPhone/CordovaLib/Classes/CDVContact.m
@@ -38,7 +38,9 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
if ((self = [super init]) != nil) {
ABRecordRef rec = ABPersonCreate();
self.record = rec;
- CFRelease(rec);
+ if (rec) {
+ CFRelease(rec);
+ }
}
return self;
}
@@ -167,9 +169,9 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
[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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
// 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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
}
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
[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 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
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/iPhone/CordovaLib/Classes/CDVContacts.h b/iPhone/CordovaLib/Classes/CDVContacts.h
index 17470c0..0342f5b 100755
--- a/iPhone/CordovaLib/Classes/CDVContacts.h
+++ b/iPhone/CordovaLib/Classes/CDVContacts.h
@@ -24,9 +24,9 @@
#import "CDVContact.h"
@interface CDVContacts : CDVPlugin <ABNewPersonViewControllerDelegate,
- ABPersonViewControllerDelegate,
- ABPeoplePickerNavigationControllerDelegate
- >
+ 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/iPhone/CordovaLib/Classes/CDVContacts.m b/iPhone/CordovaLib/Classes/CDVContacts.m
index 3faf6ba..6cb9f08 100755
--- a/iPhone/CordovaLib/Classes/CDVContacts.m
+++ b/iPhone/CordovaLib/Classes/CDVContacts.m
@@ -77,26 +77,26 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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/iPhone/CordovaLib/Classes/CDVDebugConsole.m b/iPhone/CordovaLib/Classes/CDVDebugConsole.m
deleted file mode 100755
index 29cbb91..0000000
--- a/iPhone/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/iPhone/CordovaLib/Classes/CDVDevice.m b/iPhone/CordovaLib/Classes/CDVDevice.m
index cc7ad89..a331b81 100755
--- a/iPhone/CordovaLib/Classes/CDVDevice.m
+++ b/iPhone/CordovaLib/Classes/CDVDevice.m
@@ -75,7 +75,6 @@
[devProps setObject:@"iOS" forKey:@"platform"];
[devProps setObject:[device systemVersion] forKey:@"version"];
[devProps setObject:[device uniqueAppInstanceIdentifier] forKey:@"uuid"];
- [devProps setObject:[device model] forKey:@"name"];
[devProps setObject:[[self class] cordovaVersion] forKey:@"cordova"];
NSDictionary* devReturn = [NSDictionary dictionaryWithDictionary:devProps];
diff --git a/iPhone/CordovaLib/Classes/CDVEcho.m b/iPhone/CordovaLib/Classes/CDVEcho.m
index 916e315..c74990d 100755
--- a/iPhone/CordovaLib/Classes/CDVEcho.m
+++ b/iPhone/CordovaLib/Classes/CDVEcho.m
@@ -51,4 +51,11 @@
[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/iPhone/CordovaLib/Classes/CDVExif.h b/iPhone/CordovaLib/Classes/CDVExif.h
new file mode 100755
index 0000000..3e8adbd
--- /dev/null
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVFile.h b/iPhone/CordovaLib/Classes/CDVFile.h
index 4862921..017b066 100755
--- a/iPhone/CordovaLib/Classes/CDVFile.h
+++ b/iPhone/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;
@@ -88,7 +90,8 @@ extern NSString* const kCDVAssetsLibraryPrefix;
// - (BOOL) fileExists:(NSString*)fileName;
// - (BOOL) directoryExists:(NSString*)dirName;
-- (void)writeToFile:(NSString*)fileName withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId;
+- (void)writeToFile:(NSString*)fileName withData:(NSData*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId;
+- (void)writeToFile:(NSString*)fileName withString:(NSString*)data encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId;
- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos;
@property (nonatomic, strong) NSString* appDocsPath;
diff --git a/iPhone/CordovaLib/Classes/CDVFile.m b/iPhone/CordovaLib/Classes/CDVFile.m
index d52405d..9487dd4 100755
--- a/iPhone/CordovaLib/Classes/CDVFile.m
+++ b/iPhone/CordovaLib/Classes/CDVFile.m
@@ -52,7 +52,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
}
};
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
} 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
[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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// 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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
[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 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
[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* const kCDVAssetsLibraryPrefix = @"assets-library://";
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";
@@ -1271,7 +1303,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
* IN:
* NSArray* arguments
* 0 - NSString* file path to write to
- * 1 - NSString* data to write
+ * 1 - NSString* or NSData* data to write
* 2 - NSNumber* position to begin writing
*/
- (void)write:(CDVInvokedUrlCommand*)command
@@ -1281,7 +1313,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
// arguments
NSString* argPath = [arguments objectAtIndex:0];
- NSString* argData = [arguments objectAtIndex:1];
+ id argData = [arguments objectAtIndex:1];
unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue];
// text can't be written into assets-library files
@@ -1295,15 +1327,22 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
[self truncateFile:fullPath atPosition:pos];
- [self writeToFile:fullPath withData:argData append:YES callback:callbackId];
+ if ([argData isKindOfClass:[NSString class]]) {
+ [self writeToFile:fullPath withString:argData encoding:NSUTF8StringEncoding append:YES callback:callbackId];
+ } else if ([argData isKindOfClass:[NSData class]]) {
+ [self writeToFile:fullPath withData:argData append:YES callback:callbackId];
+ } else {
+ CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+
}
-- (void)writeToFile:(NSString*)filePath withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId
+- (void)writeToFile:(NSString*)filePath withData:(NSData*)encData append:(BOOL)shouldAppend callback:(NSString*)callbackId
{
CDVPluginResult* result = nil;
CDVFileError errCode = INVALID_MODIFICATION_ERR;
int bytesWritten = 0;
- NSData* encData = [data dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
if (filePath) {
NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
@@ -1333,6 +1372,11 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}
+- (void)writeToFile:(NSString*)filePath withString:(NSString*)stringData encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId
+{
+ [self writeToFile:filePath withData:[stringData dataUsingEncoding:encoding allowLossyConversion:YES] append:shouldAppend callback:callbackId];
+}
+
- (void)testFileExists:(CDVInvokedUrlCommand*)command
{
// arguments
@@ -1343,7 +1387,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
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 +1403,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
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/iPhone/CordovaLib/Classes/CDVFileTransfer.h b/iPhone/CordovaLib/Classes/CDVFileTransfer.h
index f96bb7d..35e3fdd 100755
--- a/iPhone/CordovaLib/Classes/CDVFileTransfer.h
+++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.h
@@ -48,15 +48,21 @@ 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;
+- (void)cancelTransfer:(NSURLConnection*)connection;
+
@property (strong) NSMutableData* responseData; // atomic
@property (nonatomic, strong) CDVFileTransfer* command;
@property (nonatomic, assign) CDVFileTransferDirection direction;
@@ -70,5 +76,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/iPhone/CordovaLib/Classes/CDVFileTransfer.m b/iPhone/CordovaLib/Classes/CDVFileTransfer.m
index 4ccdce6..0f6b174 100755
--- a/iPhone/CordovaLib/Classes/CDVFileTransfer.m
+++ b/iPhone/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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// 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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
[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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// 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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
[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 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
} 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];
}
@@ -333,13 +348,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
CDVFileTransferDelegate* delegate = [activeTransfers objectForKey:objectId];
if (delegate != nil) {
- [delegate.connection cancel];
- [activeTransfers removeObjectForKey:objectId];
-
- // delete uncomplete file
- NSFileManager* fileMgr = [NSFileManager defaultManager];
- [fileMgr removeItemAtPath:delegate.target error:nil];
-
+ [delegate cancelTransfer:delegate.connection];
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]];
[self.commandDelegate sendPluginResult:result callbackId:delegate.callbackId];
}
@@ -352,6 +361,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
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 +398,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
- [self applyRequestHeaders:nil toRequest:req];
+ [self applyRequestHeaders:headers toRequest:req];
CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init];
delegate.command = self;
@@ -413,8 +423,12 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:3];
[result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
- [result setObject:source forKey:@"source"];
- [result setObject:target forKey:@"target"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
NSLog(@"FileTransferError %@", result);
return result;
@@ -429,10 +443,16 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:5];
[result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
- [result setObject:source forKey:@"source"];
- [result setObject:target forKey:@"target"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
[result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"];
- [result setObject:body forKey:@"body"];
+ if (body != nil) {
+ [result setObject:body forKey:@"body"];
+ }
NSLog(@"FileTransferError %@", result);
return result;
@@ -449,19 +469,56 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
@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 +541,15 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
}
}
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 +558,41 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
// 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)removeTargetFile
+{
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+
+ [fileMgr removeItemAtPath:self.target error:nil];
+}
+
+- (void)cancelTransfer:(NSURLConnection*)connection
+{
+ [connection cancel];
+ [self.command.activeTransfers removeObjectForKey:self.objectId];
+ [self removeTargetFile];
+}
+
+- (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);
+ [self cancelTransfer:connection];
+ [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 +601,11 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
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 +614,31 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
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
@@ -556,18 +648,37 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
NSLog(@"File Transfer Error: %@", [error localizedDescription]);
- // remove connection for activeTransfers
- [command.activeTransfers removeObjectForKey:objectId];
+ [self cancelTransfer:connection];
[self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
}
- (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 +722,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
{
if ((self = [super init])) {
self.responseData = [NSMutableData data];
+ self.targetFileHandle = nil;
}
return self;
}
diff --git a/iPhone/CordovaLib/Classes/CDVGlobalization.m b/iPhone/CordovaLib/Classes/CDVGlobalization.m
index 4d960cd..9eb9721 100755
--- a/iPhone/CordovaLib/Classes/CDVGlobalization.m
+++ b/iPhone/CordovaLib/Classes/CDVGlobalization.m
@@ -138,14 +138,14 @@
// 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 @@
// 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 @@
// 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 @@
}
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 @@
}
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 @@
}
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 @@
// 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 @@
}
CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault,
- currentLocale,
- style);
+ currentLocale,
+ style);
NSString* numberPattern = (__bridge NSString*)CFNumberFormatterGetFormat(formatter);
@@ -749,8 +749,8 @@
// 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/iPhone/CordovaLib/Classes/CDVInAppBrowser.h b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h
index 9ff460a..248274a 100755
--- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h
+++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h
@@ -20,33 +20,30 @@
#import "CDVPlugin.h"
#import "CDVInvokedUrlCommand.h"
#import "CDVScreenOrientationDelegate.h"
+#import "CDVWebViewDelegate.h"
@class CDVInAppBrowserViewController;
-@protocol CDVInAppBrowserNavigationDelegate <NSObject>
-
-- (void)browserLoadStart:(NSURL*)url;
-- (void)browserLoadStop:(NSURL*)url;
-- (void)browserExit;
-
-@end
-
-@interface CDVInAppBrowser : CDVPlugin <CDVInAppBrowserNavigationDelegate>
+@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;
+- (void)show:(CDVInvokedUrlCommand*)command;
@end
@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{
@private
- NSURL* _requestedURL;
NSString* _userAgent;
NSString* _prevUserAgent;
NSInteger _userAgentLockToken;
+ CDVWebViewDelegate* _webViewDelegate;
}
@property (nonatomic, strong) IBOutlet UIWebView* webView;
@@ -58,11 +55,14 @@
@property (nonatomic, strong) IBOutlet UIToolbar* toolbar;
@property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate;
-@property (nonatomic, weak) id <CDVInAppBrowserNavigationDelegate> navigationDelegate;
+@property (nonatomic, weak) CDVInAppBrowser* navigationDelegate;
+@property (nonatomic) NSURL* currentURL;
- (void)close;
- (void)navigateTo:(NSURL*)url;
- (void)showLocationBar:(BOOL)show;
+- (void)showToolBar:(BOOL)show;
+- (void)setCloseButtonTitle:(NSString*)title;
- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent;
@@ -71,6 +71,9 @@
@interface CDVInAppBrowserOptions : NSObject {}
@property (nonatomic, assign) BOOL location;
+@property (nonatomic, assign) BOOL toolbar;
+@property (nonatomic, copy) NSString* closebuttoncaption;
+
@property (nonatomic, copy) NSString* presentationstyle;
@property (nonatomic, copy) NSString* transitionstyle;
@@ -79,6 +82,7 @@
@property (nonatomic, assign) BOOL allowinlinemediaplayback;
@property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction;
@property (nonatomic, assign) BOOL suppressesincrementalrendering;
+@property (nonatomic, assign) BOOL hidden;
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;
diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m
index 14671a5..b832e23 100755
--- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m
+++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m
@@ -20,6 +20,7 @@
#import "CDVInAppBrowser.h"
#import "CDVPluginResult.h"
#import "CDVUserAgentUtil.h"
+#import "CDVJSON.h"
#define kInAppBrowserTargetSelf @"_self"
#define kInAppBrowserTargetSystem @"_system"
@@ -100,15 +101,19 @@
}
}
+
CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
[self.inAppBrowserViewController showLocationBar:browserOptions.location];
-
+ [self.inAppBrowserViewController showToolBar:browserOptions.toolbar];
+ if (browserOptions.closebuttoncaption != nil) {
+ [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption];
+ }
// Set Presentation Style
UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
if (browserOptions.presentationstyle != nil) {
- if ([browserOptions.presentationstyle isEqualToString:@"pagesheet"]) {
+ if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) {
presentationStyle = UIModalPresentationPageSheet;
- } else if ([browserOptions.presentationstyle isEqualToString:@"formsheet"]) {
+ } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) {
presentationStyle = UIModalPresentationFormSheet;
}
}
@@ -117,14 +122,15 @@
// Set Transition Style
UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
if (browserOptions.transitionstyle != nil) {
- if ([browserOptions.transitionstyle isEqualToString:@"fliphorizontal"]) {
+ if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) {
transitionStyle = UIModalTransitionStyleFlipHorizontal;
- } else if ([browserOptions.transitionstyle isEqualToString:@"crossdissolve"]) {
+ } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) {
transitionStyle = UIModalTransitionStyleCrossDissolve;
}
}
self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
+
// UIWebView options
self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
@@ -133,13 +139,20 @@
self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
}
-
- if (self.viewController.modalViewController != self.inAppBrowserViewController) {
+
+ if (! browserOptions.hidden) {
+ if (self.viewController.modalViewController != self.inAppBrowserViewController) {
[self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
+ }
}
[self.inAppBrowserViewController navigateTo:url];
}
+- (void)show:(CDVInvokedUrlCommand*)command
+{
+ [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
+}
+
- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
{
if ([self.commandDelegate URLIsWhitelisted:url]) {
@@ -159,24 +172,162 @@
}
}
-#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://<callbackId>/<arguments>
+ *
+ * where <callbackId> 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)browserLoadStop:(NSURL*)url
+- (void)webViewDidStartLoad:(UIWebView*)theWebView
+{
+ _injectedIframeBridge = NO;
+}
+
+- (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 +338,7 @@
{
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 +354,15 @@
@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 +383,7 @@
[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;
@@ -259,9 +413,6 @@
self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
self.closeButton.enabled = YES;
- self.closeButton.imageInsets = UIEdgeInsetsZero;
- self.closeButton.style = UIBarButtonItemStylePlain;
- self.closeButton.width = 32.000;
UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
@@ -325,32 +476,132 @@
[self.view addSubview:self.spinner];
}
+- (void)setCloseButtonTitle:(NSString*)title
+{
+ // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically
+ // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one)
+ self.closeButton = nil;
+ self.closeButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)];
+ self.closeButton.enabled = YES;
+ self.closeButton.tintColor = [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1];
+
+ NSMutableArray* items = [self.toolbar.items mutableCopy];
+ [items replaceObjectAtIndex:0 withObject:self.closeButton];
+ [self.toolbar setItems:items];
+}
+
- (void)showLocationBar:(BOOL)show
{
- CGRect addressLabelFrame = self.addressLabel.frame;
- BOOL locationBarVisible = (addressLabelFrame.size.height > 0);
+ CGRect locationbarFrame = self.addressLabel.frame;
+
+ BOOL toolbarVisible = !self.toolbar.hidden;
+
+ // prevent double show/hide
+ if (show == !(self.addressLabel.hidden)) {
+ return;
+ }
+
+ if (show) {
+ self.addressLabel.hidden = NO;
+
+ if (toolbarVisible) {
+ // toolBar at the bottom, leave as is
+ // put locationBar on top of the toolBar
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= FOOTER_HEIGHT;
+ self.webView.frame = webViewBounds;
+
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ } else {
+ // no toolBar, so put locationBar at the bottom
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
+ self.webView.frame = webViewBounds;
+
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ }
+ } else {
+ self.addressLabel.hidden = YES;
+
+ if (toolbarVisible) {
+ // locationBar is on top of toolBar, hide locationBar
+
+ // webView take up whole height less toolBar height
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= TOOLBAR_HEIGHT;
+ self.webView.frame = webViewBounds;
+ } else {
+ // no toolBar, expand webView to screen dimensions
+
+ CGRect webViewBounds = self.view.bounds;
+ self.webView.frame = webViewBounds;
+ }
+ }
+}
+
+- (void)showToolBar:(BOOL)show
+{
+ CGRect toolbarFrame = self.toolbar.frame;
+ CGRect locationbarFrame = self.addressLabel.frame;
+
+ BOOL locationbarVisible = !self.addressLabel.hidden;
// prevent double show/hide
- if (locationBarVisible == show) {
+ if (show == !(self.toolbar.hidden)) {
return;
}
if (show) {
- CGRect webViewBounds = self.view.bounds;
- webViewBounds.size.height -= FOOTER_HEIGHT;
- self.webView.frame = webViewBounds;
+ self.toolbar.hidden = NO;
+
+ if (locationbarVisible) {
+ // locationBar at the bottom, move locationBar up
+ // put toolBar at the bottom
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= FOOTER_HEIGHT;
+ self.webView.frame = webViewBounds;
+
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
- CGRect addressLabelFrame = self.addressLabel.frame;
- addressLabelFrame.size.height = LOCATIONBAR_HEIGHT;
- self.addressLabel.frame = addressLabelFrame;
+ toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT);
+ self.toolbar.frame = toolbarFrame;
+ } else {
+ // no locationBar, so put toolBar at the bottom
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= TOOLBAR_HEIGHT;
+ self.webView.frame = webViewBounds;
+
+ toolbarFrame.origin.y = webViewBounds.size.height;
+ self.toolbar.frame = toolbarFrame;
+ }
} else {
- CGRect webViewBounds = self.view.bounds;
- webViewBounds.size.height -= TOOLBAR_HEIGHT;
- self.webView.frame = webViewBounds;
+ self.toolbar.hidden = YES;
- CGRect addressLabelFrame = self.addressLabel.frame;
- addressLabelFrame.size.height = 0;
- self.addressLabel.frame = addressLabelFrame;
+ if (locationbarVisible) {
+ // locationBar is on top of toolBar, hide toolBar
+ // put locationBar at the bottom
+
+ // webView take up whole height less locationBar height
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
+ self.webView.frame = webViewBounds;
+
+ // move locationBar down
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ } else {
+ // no locationBar, expand webView to screen dimensions
+
+ CGRect webViewBounds = self.view.bounds;
+ self.webView.frame = webViewBounds;
+ }
}
}
@@ -376,6 +627,8 @@
[[self parentViewController] dismissModalViewControllerAnimated:YES];
}
+ self.currentURL = nil;
+
if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
[self.navigationDelegate browserExit];
}
@@ -385,16 +638,14 @@
{
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 +671,24 @@
[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 +705,12 @@
// 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 +723,8 @@
[self.spinner stopAnimating];
self.addressLabel.text = @"Load Error";
+
+ [self.navigationDelegate webView:theWebView didFailLoadWithError:error];
}
#pragma mark CDVScreenOrientationDelegate
@@ -510,12 +764,15 @@
if (self = [super init]) {
// default values
self.location = YES;
+ self.toolbar = YES;
+ self.closebuttoncaption = nil;
self.enableviewportscale = NO;
self.mediaplaybackrequiresuseraction = NO;
self.allowinlinemediaplayback = NO;
self.keyboarddisplayrequiresuseraction = YES;
self.suppressesincrementalrendering = NO;
+ self.hidden = NO;
}
return self;
@@ -534,19 +791,20 @@
if ([keyvalue count] == 2) {
NSString* key = [[keyvalue objectAtIndex:0] lowercaseString];
- NSString* value = [[keyvalue objectAtIndex:1] lowercaseString];
+ NSString* value = [keyvalue objectAtIndex:1];
+ NSString* value_lc = [value lowercaseString];
- BOOL isBoolean = [value isEqualToString:@"yes"] || [value isEqualToString:@"no"];
+ BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"];
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setAllowsFloats:YES];
- BOOL isNumber = [numberFormatter numberFromString:value] != nil;
+ BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil;
// set the property according to the key name
if ([obj respondsToSelector:NSSelectorFromString(key)]) {
if (isNumber) {
- [obj setValue:[numberFormatter numberFromString:value] forKey:key];
+ [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key];
} else if (isBoolean) {
- [obj setValue:[NSNumber numberWithBool:[value isEqualToString:@"yes"]] forKey:key];
+ [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key];
} else {
[obj setValue:value forKey:key];
}
diff --git a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h
index 6eb0099..7be8884 100755
--- a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h
new file mode 100755
index 0000000..3b43ef0
--- /dev/null
+++ b/iPhone/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 <Foundation/Foundation.h>
+
+@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/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m
new file mode 100755
index 0000000..93cafb8
--- /dev/null
+++ b/iPhone/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 : @"<ffe1>"]) {
+ // 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:&currentDataOffset];
+
+ //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:&currentDataOffset];
+ /*
+ 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/iPhone/CordovaLib/Classes/CDVLocalStorage.h b/iPhone/CordovaLib/Classes/CDVLocalStorage.h
index cc6613f..dec6ab3 100755
--- a/iPhone/CordovaLib/Classes/CDVLocalStorage.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVLocalStorage.m b/iPhone/CordovaLib/Classes/CDVLocalStorage.m
index 68175f1..238d680 100755
--- a/iPhone/CordovaLib/Classes/CDVLocalStorage.m
+++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.m
@@ -35,7 +35,7 @@
{
[[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 @@
// ////////// 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 @@
// ////////// 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 @@
// ////////// 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*)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 @@
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 @@
// 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 @@
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 @@
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/iPhone/CordovaLib/Classes/CDVLocation.h b/iPhone/CordovaLib/Classes/CDVLocation.h
index 7087d43..caf0798 100755
--- a/iPhone/CordovaLib/Classes/CDVLocation.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVLocation.m b/iPhone/CordovaLib/Classes/CDVLocation.m
index 07af30e..ed9ec26 100755
--- a/iPhone/CordovaLib/Classes/CDVLocation.m
+++ b/iPhone/CordovaLib/Classes/CDVLocation.m
@@ -591,17 +591,17 @@
- (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 @@
- (NSString*)JSONRepresentation
{
return [NSString stringWithFormat:
- @"{ code: %d, message: '%@'}",
- self.code,
- [self localizedDescription]
+ @"{ code: %d, message: '%@'}",
+ self.code,
+ [self localizedDescription]
];
}
diff --git a/iPhone/CordovaLib/Classes/CDVNotification.h b/iPhone/CordovaLib/Classes/CDVNotification.h
index 1eedb54..5b5b89f 100755
--- a/iPhone/CordovaLib/Classes/CDVNotification.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVNotification.m b/iPhone/CordovaLib/Classes/CDVNotification.m
index 992239e..464eb1f 100755
--- a/iPhone/CordovaLib/Classes/CDVNotification.m
+++ b/iPhone/CordovaLib/Classes/CDVNotification.m
@@ -20,12 +20,25 @@
#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.
+ * defaultText The input text for the textbox (if textbox exists).
+ * callbackId The commmand callback id.
+ * dialogType The type of alert view [alert | prompt].
+ */
+- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSArray*)buttons defaultText:(NSString*)defaultText callbackId:(NSString*)callbackId dialogType:(NSString*)dialogType
{
CDVAlertView* alertView = [[CDVAlertView alloc]
- initWithTitle:title
+ initWithTitle:title
message:message
delegate:self
cancelButtonTitle:nil
@@ -33,11 +46,16 @@
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;
+ UITextField* textField = [alertView textFieldAtIndex:0];
+ textField.text = defaultText;
}
[alertView show];
@@ -46,52 +64,55 @@
- (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] defaultText:nil 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 defaultText:nil 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];
+ NSString* defaultText = [command argumentAtIndex:3];
- [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId];
+ [self showDialogWithMessage:message title:title buttons:buttons defaultText:defaultText 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/iPhone/CordovaLib/Classes/CDVPlugin.h b/iPhone/CordovaLib/Classes/CDVPlugin.h
index f5b50eb..33ba1c4 100755
--- a/iPhone/CordovaLib/Classes/CDVPlugin.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVPlugin.m b/iPhone/CordovaLib/Classes/CDVPlugin.m
index a42d241..8c932a0 100755
--- a/iPhone/CordovaLib/Classes/CDVPlugin.m
+++ b/iPhone/CordovaLib/Classes/CDVPlugin.m
@@ -19,6 +19,11 @@
#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 @@
[[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 @@
// [[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/iPhone/CordovaLib/Classes/CDVPluginResult.h b/iPhone/CordovaLib/Classes/CDVPluginResult.h
index 8683205..11b5377 100755
--- a/iPhone/CordovaLib/Classes/CDVPluginResult.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVPluginResult.m b/iPhone/CordovaLib/Classes/CDVPluginResult.m
index d9ba08f..af7c528 100755
--- a/iPhone/CordovaLib/Classes/CDVPluginResult.m
+++ b/iPhone/CordovaLib/Classes/CDVPluginResult.m
@@ -29,10 +29,40 @@
@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 @@ static NSArray* org_apache_cordova_CommandStatusMsgs;
+ (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 @@ static NSArray* org_apache_cordova_CommandStatusMsgs;
[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/iPhone/CordovaLib/Classes/CDVReachability.m b/iPhone/CordovaLib/Classes/CDVReachability.m
index 3c5a48b..89f4ec9 100755
--- a/iPhone/CordovaLib/Classes/CDVReachability.m
+++ b/iPhone/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 @@ static void CDVReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRe
}
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/iPhone/CordovaLib/Classes/CDVSound.m b/iPhone/CordovaLib/Classes/CDVSound.m
index 99515d7..71eab59 100755
--- a/iPhone/CordovaLib/Classes/CDVSound.m
+++ b/iPhone/CordovaLib/Classes/CDVSound.m
@@ -87,7 +87,7 @@
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 @@
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 @@
} 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 @@
// 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 @@
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 @@
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/iPhone/CordovaLib/Classes/CDVSplashScreen.h b/iPhone/CordovaLib/Classes/CDVSplashScreen.h
index a0868a0..704ab43 100755
--- a/iPhone/CordovaLib/Classes/CDVSplashScreen.h
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVSplashScreen.m b/iPhone/CordovaLib/Classes/CDVSplashScreen.m
index cba1b53..fdb79fa 100755
--- a/iPhone/CordovaLib/Classes/CDVSplashScreen.m
+++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.m
@@ -19,29 +19,25 @@
#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 @@
// 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,125 @@
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)updateSplashImageForOrientation:(UIInterfaceOrientation)orientation
+- (void)destroyViews
{
- // IPHONE (default)
- NSString* imageName = @"Default";
+ [_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"];
+}
+
+// Sets the view's frame and image.
+- (void)updateImage
+{
+ 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 (orientation == UIInterfaceOrientationLandscapeLeft) {
- imageName = @"Default-Landscape.png"; // @"Default-LandscapeLeft.png";
- } else if (orientation == UIInterfaceOrientationLandscapeRight) {
- imageName = @"Default-Landscape.png"; // @"Default-LandscapeRight.png";
+ if (![imageName isEqualToString:_curImageName]) {
+ UIImage* img = [UIImage imageNamed:imageName];
+ _imageView.image = img;
+ _curImageName = imageName;
+ }
+
+ // Check that splash screen's image exists before updating bounds
+ if (_imageView.image) {
+ [self updateBounds];
+ } else {
+ NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName);
+ }
+}
+
+- (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 +204,26 @@
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/iPhone/CordovaLib/Classes/CDVDebugConsole.h b/iPhone/CordovaLib/Classes/CDVTimer.h
index 6a0a185..6d31593 100755
--- a/iPhone/CordovaLib/Classes/CDVDebugConsole.h
+++ b/iPhone/CordovaLib/Classes/CDVTimer.h
@@ -18,11 +18,10 @@
*/
#import <Foundation/Foundation.h>
-#import <UIKit/UIKit.h>
-#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/iPhone/CordovaLib/Classes/CDVTimer.m b/iPhone/CordovaLib/Classes/CDVTimer.m
new file mode 100755
index 0000000..784e94d
--- /dev/null
+++ b/iPhone/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/iPhone/CordovaLib/Classes/CDVURLProtocol.m b/iPhone/CordovaLib/Classes/CDVURLProtocol.m
index 1959c77..afc10de 100755
--- a/iPhone/CordovaLib/Classes/CDVURLProtocol.m
+++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.m
@@ -31,7 +31,7 @@
@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 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request)
[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 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request)
[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 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request)
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/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m
index 5c43c51..9923d47 100755
--- a/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m
+++ b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m
@@ -96,7 +96,7 @@ static NSMutableArray* gPendingSetUserAgentBlocks = nil;
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/iPhone/CordovaLib/Classes/CDVViewController.h b/iPhone/CordovaLib/Classes/CDVViewController.h
index 82e22f6..2338baf 100755
--- a/iPhone/CordovaLib/Classes/CDVViewController.h
+++ b/iPhone/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 <UIWebViewDelegate, CDVScreenOrientationDelegate>{
@protected
- CDVCommandDelegateImpl* _commandDelegate;
+ id <CDVCommandDelegate> _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 <CDVCommandDelegate> 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/iPhone/CordovaLib/Classes/CDVViewController.m b/iPhone/CordovaLib/Classes/CDVViewController.m
index bec716d..94f4552 100755
--- a/iPhone/CordovaLib/Classes/CDVViewController.m
+++ b/iPhone/CordovaLib/Classes/CDVViewController.m
@@ -19,7 +19,6 @@
#import <objc/message.h>
#import "CDV.h"
-#import "CDVCommandQueue.h"
#import "CDVCommandDelegateImpl.h"
#import "CDVConfigParser.h"
#import "CDVUserAgentUtil.h"
@@ -103,6 +102,48 @@
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 @@
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 @@
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 @@
*/
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 @@
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 @@
}
}
- 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 @@
// /////////////////
[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:@"<html><body> %@ </body></html>", 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:@"<html><body> %@ </body></html>", 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:@"<UIPeripheralHostView"]) {
+ for (UIView* peripheralView in view.subviews) {
+ // hides the accessory bar
+ if ([[peripheralView description] hasPrefix:@"<UIWebFormAccessory"]) {
+ // remove the extra scroll space for the form accessory bar
+ CGRect newFrame = self.webView.scrollView.frame;
+ newFrame.size.height += peripheralView.frame.size.height;
+ self.webView.scrollView.frame = newFrame;
+
+ // remove the form accessory bar
+ [peripheralView removeFromSuperview];
+ }
+ // hides the thin grey line used to adorn the bar (iOS 6)
+ if ([[peripheralView description] hasPrefix:@"<UIImageView"]) {
+ [[peripheralView layer] setOpacity:0.0];
+ }
+ }
}
- }];
+ }
+ }
}
- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations
@@ -518,12 +615,6 @@
// It's safe to release the lock even if this is just a sub-frame that's finished loading.
[CDVUserAgentUtil releaseLock:&_userAgentLockToken];
- // The .onNativeReady().fire() will work when cordova.js is already loaded.
- // The _nativeReady = true; is used when this is run before cordova.js is loaded.
- NSString* nativeReady = @"try{cordova.require('cordova/channel').onNativeReady.fire();}catch(e){window._nativeReady = true;}";
- // Don't use [commandDelegate evalJs] here since it relies on cordova.js being loaded already.
- [self.webView stringByEvaluatingJavaScriptFromString:nativeReady];
-
/*
* Hide the Top Activity THROBBER in the Battery Bar
*/
@@ -531,7 +622,7 @@
[self processOpenUrl];
- [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:nil]];
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:self.webView]];
}
- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
@@ -660,6 +751,22 @@
[plugin pluginInitialize];
}
+- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName
+{
+ if ([plugin respondsToSelector:@selector(setViewController:)]) {
+ [plugin setViewController:self];
+ }
+
+ if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
+ [plugin setCommandDelegate:_commandDelegate];
+ }
+
+ NSString* className = NSStringFromClass([plugin class]);
+ [self.pluginObjects setObject:plugin forKey:className];
+ [self.pluginsMap setValue:className forKey:[pluginName lowercaseString]];
+ [plugin pluginInitialize];
+}
+
/**
Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned.
*/
@@ -678,7 +785,7 @@
id obj = [self.pluginObjects objectForKey:className];
if (!obj) {
- obj = [[NSClassFromString (className)alloc] initWithWebView:webView];
+ obj = [[NSClassFromString(className)alloc] initWithWebView:webView];
if (obj != nil) {
[self registerPlugin:obj withClassName:className];
@@ -814,13 +921,8 @@
- (void)dealloc
{
[CDVURLProtocol unregisterViewController:self];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:CDVPluginHandleOpenURLNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
self.webView.delegate = nil;
self.webView = nil;
[CDVUserAgentUtil releaseLock:&_userAgentLockToken];
diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h
index 8a89a22..dd71807 100755
--- a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h
+++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h
@@ -30,8 +30,10 @@
NSInteger _loadCount;
NSInteger _state;
NSInteger _curLoadToken;
+ NSInteger _loadStartPollCount;
}
- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate;
+- (BOOL)request:(NSURLRequest*)newRequest isFragmentIdentifierToRequest:(NSURLRequest*)originalRequest;
@end
diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m
index 9ee8186..7254c2e 100755
--- a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m
+++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m
@@ -17,14 +17,77 @@
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,11 +98,50 @@ typedef enum {
if (self != nil) {
_delegate = delegate;
_loadCount = -1;
- _state = STATE_NORMAL;
+ _state = STATE_IDLE;
}
return self;
}
+- (BOOL)request:(NSURLRequest*)newRequest isFragmentIdentifierToRequest:(NSURLRequest*)originalRequest
+{
+ if (originalRequest.URL && newRequest.URL) {
+ NSString* originalRequestUrl = [originalRequest.URL absoluteString];
+ NSString* newRequestUrl = [newRequest.URL absoluteString];
+
+ // no fragment, easy
+ if (newRequest.URL.fragment == nil) {
+ return NO;
+ }
+
+ // if the urls have fragments and they are equal
+ if ((originalRequest.URL.fragment && newRequest.URL.fragment) && [originalRequestUrl isEqualToString:newRequestUrl]) {
+ return YES;
+ }
+
+ NSString* urlFormat = @"%@://%@:%d/%@#%@";
+ // reconstruct the URLs (ignoring basic auth credentials, query string)
+ NSString* baseOriginalRequestUrl = [NSString stringWithFormat:urlFormat,
+ [originalRequest.URL scheme],
+ [originalRequest.URL host],
+ [[originalRequest.URL port] intValue],
+ [originalRequest.URL path],
+ [newRequest.URL fragment] // add the new request's fragment
+ ];
+ NSString* baseNewRequestUrl = [NSString stringWithFormat:urlFormat,
+ [newRequest.URL scheme],
+ [newRequest.URL host],
+ [[newRequest.URL port] intValue],
+ [newRequest.URL path],
+ [newRequest.URL fragment]
+ ];
+
+ return [baseOriginalRequestUrl isEqualToString:baseNewRequestUrl];
+ }
+
+ return NO;
+}
+
- (BOOL)isPageLoaded:(UIWebView*)webView
{
NSString* readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];
@@ -62,95 +164,216 @@ typedef enum {
- (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:
+ {
+ NSString* description = [NSString stringWithFormat:@"CDVWebViewDelegate: Navigation started when state=%d", _state];
+ NSLog(@"%@", description);
+ _loadCount = 0;
+ _state = STATE_WAITING_FOR_LOAD_START;
+ if (![self request:request isFragmentIdentifierToRequest:webView.request]) {
+ if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
+ NSDictionary* errorDictionary = @{NSLocalizedDescriptionKey : description};
+ NSError* error = [[NSError alloc] initWithDomain:@"CDVWebViewDelegate" code:1 userInfo:errorDictionary];
+ [_delegate webView:webView didFailLoadWithError:error];
+ }
+ }
+ }
+ }
+ } 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/iPhone/CordovaLib/Classes/CDVWhitelist.h b/iPhone/CordovaLib/Classes/CDVWhitelist.h
index 3741e94..9165097 100755
--- a/iPhone/CordovaLib/Classes/CDVWhitelist.h
+++ b/iPhone/CordovaLib/Classes/CDVWhitelist.h
@@ -23,14 +23,12 @@ 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;
+- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure;
- (NSString*)errorStringForURL:(NSURL*)url;
@end
diff --git a/iPhone/CordovaLib/Classes/CDVWhitelist.m b/iPhone/CordovaLib/Classes/CDVWhitelist.m
index 77e20ac..e6807bd 100755
--- a/iPhone/CordovaLib/Classes/CDVWhitelist.m
+++ b/iPhone/CordovaLib/Classes/CDVWhitelist.m
@@ -20,12 +20,12 @@
#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 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio
@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* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio
}
}
+- (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,99 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio
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:[NSMutableArray 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) {
+ return [self URLIsAllowed:url logFailure:YES];
+}
+
+- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure
+{
+ 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 logFailure:NO]) {
+ return YES;
+ }
+ }
+
+ // Check that the scheme is supported
+ if (![self schemeIsAllowed:scheme]) {
+ if (logFailure) {
+ NSLog(@"%@", [self errorStringForURL:url]);
+ }
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];
@@ -173,13 +212,20 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio
while (regex = [enumerator nextObject]) {
NSPredicate* regex_test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
+ // if wildcard, break out and allow
+ if ([regex isEqualToString:@"*"]) {
+ return YES;
+ }
+
if ([regex_test evaluateWithObject:urlHost] == YES) {
// if it matches at least one rule, return
return YES;
}
}
- NSLog(@"%@", [self errorStringForURL:url]);
+ if (logFailure) {
+ NSLog(@"%@", [self errorStringForURL:url]);
+ }
// if we got here, the url host is not in the white-list, do nothing
return NO;
}
diff --git a/iPhone/CordovaLib/Classes/NSData+Base64.m b/iPhone/CordovaLib/Classes/NSData+Base64.m
index 08c801b..d0f2189 100755
--- a/iPhone/CordovaLib/Classes/NSData+Base64.m
+++ b/iPhone/CordovaLib/Classes/NSData+Base64.m
@@ -249,13 +249,10 @@ char *CDVNewBase64Encode(
//
+ (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 @@ char *CDVNewBase64Encode(
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/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m
index 80e9ac1..0361ff9 100755
--- a/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m
+++ b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m
@@ -28,7 +28,7 @@
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/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h
deleted file mode 100755
index 9794fa2..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h
+++ /dev/null
@@ -1,30 +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.
- */
-
-// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6.
-//
-
-/*
- Returns YES if it is at least version specified as NSString(X)
- Usage:
- if (IsAtLeastiOSVersion(@"5.1")) {
- // do something for iOS 5.1 or greater
- }
- */
-#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)
diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h
deleted file mode 100755
index 7a06e51..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h
+++ /dev/null
@@ -1,46 +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.
- */
-
-// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6.
-//
-
-#ifdef PHONEGAP_FRAMEWORK
- #import <PhoneGap/PGPlugin.h>
-#else
- #import "PGPlugin.h"
-#endif
-
-typedef enum {
- CDVCommandStatus_NO_RESULT = 0,
- CDVCommandStatus_OK,
- CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION,
- CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION,
- CDVCommandStatus_INSTANTIATION_EXCEPTION,
- CDVCommandStatus_MALFORMED_URL_EXCEPTION,
- CDVCommandStatus_IO_EXCEPTION,
- CDVCommandStatus_INVALID_ACTION,
- CDVCommandStatus_JSON_EXCEPTION,
- CDVCommandStatus_ERROR
-} CDVCommandStatus;
-
-@interface CDVPlugin : PGPlugin
-@end
-
-@interface CDVPluginResult : PluginResult
-@end
diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m
deleted file mode 100755
index 52ccd41..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m
+++ /dev/null
@@ -1,29 +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.
- */
-
-// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6.
-//
-
-#import "CDVPlugin.h"
-
-@implementation CDVPlugin
-@end
-
-@implementation CDVPluginResult
-@end
diff --git a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h
deleted file mode 100755
index 1c76eaa..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h
+++ /dev/null
@@ -1,32 +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.
- */
-
-// Bridge implementation file for using Cordova > 1.5 plugins in 1.5.0.
-//
-
-#import <Cordova/CDV.h>
-
-/*
- Returns YES if it is at least version specified as NSString(X)
- Usage:
- if (IsAtLeastiOSVersion(@"5.1")) {
- // do something for iOS 5.1 or greater
- }
- */
-#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)
diff --git a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h
deleted file mode 100755
index 6e2c4c3..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h
+++ /dev/null
@@ -1,23 +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.
- */
-
-// Bridge implementation file for using Cordova > 1.5 plugins in 1.5.0.
-//
-
-#import <Cordova/CDVPlugin.h>
diff --git a/iPhone/CordovaLib/Classes/compatibility/README.txt b/iPhone/CordovaLib/Classes/compatibility/README.txt
deleted file mode 100755
index 8c2d644..0000000
--- a/iPhone/CordovaLib/Classes/compatibility/README.txt
+++ /dev/null
@@ -1,23 +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.
-#
-
-Include these headers if you are using a bleeding edge plugin in an older version of Cordova.
-
-1.5.0 -- only for 1.5.0 projects
-0.9.6 -- for projects between 0.9.6 and 1.4.1 \ No newline at end of file