diff options
97 files changed, 25299 insertions, 453 deletions
diff --git a/iPhone/CordovaLib/Classes/CDV.h b/iPhone/CordovaLib/Classes/CDV.h new file mode 100755 index 0000000..5367799 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDV.h @@ -0,0 +1,58 @@ +/* + 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 "CDVAvailability.h" + +#import "CDVPlugin.h" +#import "CDVViewController.h" +#import "CDVCommandDelegate.h" +#import "CDVURLProtocol.h" +#import "CDVInvokedUrlCommand.h" + +#import "CDVAccelerometer.h" +#import "CDVBattery.h" +#import "CDVCamera.h" +#import "CDVCapture.h" +#import "CDVConnection.h" +#import "CDVContact.h" +#import "CDVContacts.h" +#import "CDVCordovaView.h" +#import "CDVDebug.h" +#import "CDVDebugConsole.h" +#import "CDVDevice.h" +#import "CDVFile.h" +#import "CDVFileTransfer.h" +#import "CDVLocation.h" +#import "CDVNotification.h" +#import "CDVPluginResult.h" +#import "CDVReachability.h" +#import "CDVSound.h" +#import "CDVSplashScreen.h" +#import "CDVWhitelist.h" +#import "CDVLocalStorage.h" +#import "CDVInAppBrowser.h" +#import "CDVScreenOrientationDelegate.h" + +#import "NSArray+Comparisons.h" +#import "NSData+Base64.h" +#import "NSDictionary+Extensions.h" +#import "NSMutableArray+QueueAdditions.h" +#import "UIDevice+Extensions.h" + +#import "JSONKit.h" diff --git a/iPhone/CordovaLib/Classes/CDVAccelerometer.h b/iPhone/CordovaLib/Classes/CDVAccelerometer.h new file mode 100755 index 0000000..044ca53 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVAccelerometer.h @@ -0,0 +1,39 @@ +/* + 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 <UIKit/UIKit.h> +#import "CDVPlugin.h" + +@interface CDVAccelerometer : CDVPlugin <UIAccelerometerDelegate> +{ + double x; + double y; + double z; + NSTimeInterval timestamp; +} + +@property (readonly, assign) BOOL isRunning; +@property (nonatomic, strong) NSString* callbackId; + +- (CDVAccelerometer*)init; + +- (void)start:(CDVInvokedUrlCommand*)command; +- (void)stop:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVAccelerometer.m b/iPhone/CordovaLib/Classes/CDVAccelerometer.m new file mode 100755 index 0000000..33093d0 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVAccelerometer.m @@ -0,0 +1,128 @@ +/* + 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 "CDVAccelerometer.h" + +@interface CDVAccelerometer () {} +@property (readwrite, assign) BOOL isRunning; +@end + +@implementation CDVAccelerometer + +@synthesize callbackId, isRunning; + +// defaults to 10 msec +#define kAccelerometerInterval 40 +// g constant: -9.81 m/s^2 +#define kGravitationalConstant -9.81 + +- (CDVAccelerometer*)init +{ + self = [super init]; + if (self) { + x = 0; + y = 0; + z = 0; + timestamp = 0; + self.callbackId = nil; + self.isRunning = NO; + } + return self; +} + +- (void)dealloc +{ + [self stop:nil]; +} + +- (void)start:(CDVInvokedUrlCommand*)command +{ + NSString* cbId = command.callbackId; + NSTimeInterval desiredFrequency_num = kAccelerometerInterval; + UIAccelerometer* pAccel = [UIAccelerometer sharedAccelerometer]; + + // accelerometer expects fractional seconds, but we have msecs + pAccel.updateInterval = desiredFrequency_num / 1000; + self.callbackId = cbId; + if (!self.isRunning) { + pAccel.delegate = self; + self.isRunning = YES; + } +} + +- (void)onReset +{ + [self stop:nil]; +} + +- (void)stop:(CDVInvokedUrlCommand*)command +{ + UIAccelerometer* theAccelerometer = [UIAccelerometer sharedAccelerometer]; + + theAccelerometer.delegate = nil; + self.isRunning = NO; +} + +/** + * Picks up accel updates from device and stores them in this class + */ +- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration +{ + if (self.isRunning) { + x = acceleration.x; + y = acceleration.y; + z = acceleration.z; + timestamp = ([[NSDate date] timeIntervalSince1970] * 1000); + [self returnAccelInfo]; + } +} + +- (void)returnAccelInfo +{ + // Create an acceleration object + NSMutableDictionary* accelProps = [NSMutableDictionary dictionaryWithCapacity:4]; + + [accelProps setValue:[NSNumber numberWithDouble:x * kGravitationalConstant] forKey:@"x"]; + [accelProps setValue:[NSNumber numberWithDouble:y * kGravitationalConstant] forKey:@"y"]; + [accelProps setValue:[NSNumber numberWithDouble:z * kGravitationalConstant] forKey:@"z"]; + [accelProps setValue:[NSNumber numberWithDouble:timestamp] forKey:@"timestamp"]; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:accelProps]; + [result setKeepCallback:[NSNumber numberWithBool:YES]]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; +} + +// TODO: Consider using filtering to isolate instantaneous data vs. gravity data -jm + +/* + #define kFilteringFactor 0.1 + + // Use a basic low-pass filter to keep only the gravity component of each axis. + grav_accelX = (acceleration.x * kFilteringFactor) + ( grav_accelX * (1.0 - kFilteringFactor)); + grav_accelY = (acceleration.y * kFilteringFactor) + ( grav_accelY * (1.0 - kFilteringFactor)); + grav_accelZ = (acceleration.z * kFilteringFactor) + ( grav_accelZ * (1.0 - kFilteringFactor)); + + // Subtract the low-pass value from the current value to get a simplified high-pass filter + instant_accelX = acceleration.x - ( (acceleration.x * kFilteringFactor) + (instant_accelX * (1.0 - kFilteringFactor)) ); + instant_accelY = acceleration.y - ( (acceleration.y * kFilteringFactor) + (instant_accelY * (1.0 - kFilteringFactor)) ); + instant_accelZ = acceleration.z - ( (acceleration.z * kFilteringFactor) + (instant_accelZ * (1.0 - kFilteringFactor)) ); + + + */ +@end diff --git a/iPhone/CordovaLib/Classes/CDVAvailability.h b/iPhone/CordovaLib/Classes/CDVAvailability.h new file mode 100755 index 0000000..d2e7f1b --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVAvailability.h @@ -0,0 +1,74 @@ +/* + 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. + */ + +#define __CORDOVA_0_9_6 906 +#define __CORDOVA_1_0_0 10000 +#define __CORDOVA_1_1_0 10100 +#define __CORDOVA_1_2_0 10200 +#define __CORDOVA_1_3_0 10300 +#define __CORDOVA_1_4_0 10400 +#define __CORDOVA_1_4_1 10401 +#define __CORDOVA_1_5_0 10500 +#define __CORDOVA_1_6_0 10600 +#define __CORDOVA_1_6_1 10601 +#define __CORDOVA_1_7_0 10700 +#define __CORDOVA_1_8_0 10800 +#define __CORDOVA_1_8_1 10801 +#define __CORDOVA_1_9_0 10900 +#define __CORDOVA_2_0_0 20000 +#define __CORDOVA_2_1_0 20100 +#define __CORDOVA_2_2_0 20200 +#define __CORDOVA_2_3_0 20300 +#define __CORDOVA_NA 99999 /* not available */ + +/* + #if CORDOVA_VERSION_MIN_REQUIRED >= __CORDOVA_1_7_0 + // do something when its at least 1.7.0 + #else + // do something else (non 1.7.0) + #endif + */ +#ifndef CORDOVA_VERSION_MIN_REQUIRED + #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_3_0 +#endif + +/* + 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) + +#define CDV_IsIPad() ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] && ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)) + +#define CDV_IsIPhone5() ([[UIScreen mainScreen] bounds].size.height == 568 && [[UIScreen mainScreen] bounds].size.width == 320) + +/* 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] + +#ifdef __clang__ + #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg))) +#else + #define CDV_DEPRECATED(version, msg) __attribute__((deprecated())) +#endif diff --git a/iPhone/CordovaLib/Classes/CDVBattery.h b/iPhone/CordovaLib/Classes/CDVBattery.h new file mode 100755 index 0000000..ba26c3a --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVBattery.h @@ -0,0 +1,40 @@ +/* + 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> +#import "CDVPlugin.h" + +@interface CDVBattery : CDVPlugin { + UIDeviceBatteryState state; + float level; + bool isPlugged; + NSString* callbackId; +} + +@property (nonatomic) UIDeviceBatteryState state; +@property (nonatomic) float level; +@property (nonatomic) bool isPlugged; +@property (strong) NSString* callbackId; + +- (void)updateBatteryStatus:(NSNotification*)notification; +- (NSDictionary*)getBatteryStatus; +- (void)start:(CDVInvokedUrlCommand*)command; +- (void)stop:(CDVInvokedUrlCommand*)command; +- (void)dealloc; +@end diff --git a/iPhone/CordovaLib/Classes/CDVBattery.m b/iPhone/CordovaLib/Classes/CDVBattery.m new file mode 100755 index 0000000..681511c --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVBattery.m @@ -0,0 +1,152 @@ +/* + 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 "CDVBattery.h" + +@interface CDVBattery (PrivateMethods) +- (void)updateOnlineStatus; +@end + +@implementation CDVBattery + +@synthesize state, level, callbackId, isPlugged; + +/* determining type of event occurs on JavaScript side +- (void) updateBatteryLevel:(NSNotification*)notification +{ + // send batterylow event for less than 25% battery + // send batterycritical event for less than 10% battery + // W3c says to send batteryStatus event when batterylevel changes by more than 1% (iOS seems to notify each 5%) + // always update the navigator.device.battery info + float currentLevel = [[UIDevice currentDevice] batteryLevel]; + NSString* type = @""; + // no check for level == -1 since this api is only called when monitoring is enabled so level should be valid + if (currentLevel < 0.10){ + type = @"batterycritical"; + } else if (currentLevel < 0.25) { + type = @"batterylow"; + } else { + float onePercent = 0.1; + if (self.level >= 0 ){ + onePercent = self.level * 0.01; + } + if (fabsf(currentLevel - self.level) > onePercent){ + // issue batteryStatus event + type = @"batterystatus"; + } + } + // update the battery info and fire event + NSString* jsString = [NSString stringWithFormat:@"navigator.device.battery._status(\"%@\", %@)", type,[[self getBatteryStatus] JSONRepresentation]]; + [super writeJavascript:jsString]; +} + */ + +- (void)updateBatteryStatus:(NSNotification*)notification +{ + NSDictionary* batteryData = [self getBatteryStatus]; + + if (self.callbackId) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:batteryData]; + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; + } +} + +/* Get the current battery status and level. Status will be unknown and level will be -1.0 if + * monitoring is turned off. + */ +- (NSDictionary*)getBatteryStatus +{ + UIDevice* currentDevice = [UIDevice currentDevice]; + UIDeviceBatteryState currentState = [currentDevice batteryState]; + + isPlugged = FALSE; // UIDeviceBatteryStateUnknown or UIDeviceBatteryStateUnplugged + if ((currentState == UIDeviceBatteryStateCharging) || (currentState == UIDeviceBatteryStateFull)) { + isPlugged = TRUE; + } + float currentLevel = [currentDevice batteryLevel]; + + if ((currentLevel != self.level) || (currentState != self.state)) { + self.level = currentLevel; + self.state = currentState; + } + + // W3C spec says level must be null if it is unknown + NSObject* w3cLevel = nil; + if ((currentState == UIDeviceBatteryStateUnknown) || (currentLevel == -1.0)) { + w3cLevel = [NSNull null]; + } else { + w3cLevel = [NSNumber numberWithFloat:(currentLevel * 100)]; + } + NSMutableDictionary* batteryData = [NSMutableDictionary dictionaryWithCapacity:2]; + [batteryData setObject:[NSNumber numberWithBool:isPlugged] forKey:@"isPlugged"]; + [batteryData setObject:w3cLevel forKey:@"level"]; + return batteryData; +} + +/* turn on battery monitoring*/ +- (void)start:(CDVInvokedUrlCommand*)command +{ + self.callbackId = command.callbackId; + + if ([UIDevice currentDevice].batteryMonitoringEnabled == NO) { + [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBatteryStatus:) + name:UIDeviceBatteryStateDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBatteryStatus:) + name:UIDeviceBatteryLevelDidChangeNotification object:nil]; + } +} + +/* turn off battery monitoring */ +- (void)stop:(CDVInvokedUrlCommand*)command +{ + // callback one last time to clear the callback function on JS side + if (self.callbackId) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getBatteryStatus]]; + [result setKeepCallbackAsBool:NO]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; + } + self.callbackId = nil; + [[UIDevice currentDevice] setBatteryMonitoringEnabled:NO]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryStateDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryLevelDidChangeNotification object:nil]; +} + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVBattery*)[super initWithWebView:theWebView]; + if (self) { + self.state = UIDeviceBatteryStateUnknown; + self.level = -1.0; + } + return self; +} + +- (void)dealloc +{ + [self stop:nil]; +} + +- (void)onReset +{ + [self stop:nil]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCamera.h b/iPhone/CordovaLib/Classes/CDVCamera.h new file mode 100755 index 0000000..2905a8a --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCamera.h @@ -0,0 +1,90 @@ +/* + 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> +#import "CDVPlugin.h" + +enum CDVDestinationType { + DestinationTypeDataUrl = 0, + DestinationTypeFileUri +}; +typedef NSUInteger CDVDestinationType; + +enum CDVEncodingType { + EncodingTypeJPEG = 0, + EncodingTypePNG +}; +typedef NSUInteger CDVEncodingType; + +enum CDVMediaType { + MediaTypePicture = 0, + MediaTypeVideo, + MediaTypeAll +}; +typedef NSUInteger CDVMediaType; + +@interface CDVCameraPicker : UIImagePickerController +{} + +@property (assign) NSInteger quality; +@property (copy) NSString* callbackId; +@property (copy) NSString* postUrl; +@property (nonatomic) enum CDVDestinationType returnType; +@property (nonatomic) enum CDVEncodingType encodingType; +@property (strong) UIPopoverController* popoverController; +@property (assign) CGSize targetSize; +@property (assign) bool correctOrientation; +@property (assign) bool saveToPhotoAlbum; +@property (assign) bool cropToSize; +@property (strong) UIWebView* webView; +@property (assign) BOOL popoverSupported; + +@end + +// ======================================================================= // + +@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate, + UINavigationControllerDelegate, + UIPopoverControllerDelegate> +{} + +@property (strong) CDVCameraPicker* pickerController; + +/* + * getPicture + * + * arguments: + * 1: this is the javascript function that will be called with the results, the first parameter passed to the + * javascript function is the picture as a Base64 encoded string + * 2: this is the javascript function to be called if there was an error + * options: + * quality: integer between 1 and 100 + */ +- (void)takePicture:(CDVInvokedUrlCommand*)command; +- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url; +- (void)cleanup:(CDVInvokedUrlCommand*)command; + +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info; +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; +- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker; +- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize; +- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize; +- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCamera.m b/iPhone/CordovaLib/Classes/CDVCamera.m new file mode 100755 index 0000000..cfbf415 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCamera.m @@ -0,0 +1,533 @@ +/* + 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 "CDVCamera.h" +#import "NSArray+Comparisons.h" +#import "NSData+Base64.h" +#import "NSDictionary+Extensions.h" +#import <MobileCoreServices/UTCoreTypes.h> + +#define CDV_PHOTO_PREFIX @"cdv_photo_" + +static NSSet* org_apache_cordova_validArrowDirections; + +@interface CDVCamera () + +@property (readwrite, assign) BOOL hasPendingOperation; + +@end + +@implementation CDVCamera + ++ (void)initialize +{ + 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; + +- (BOOL)popoverSupported +{ + return (NSClassFromString(@"UIPopoverController") != nil) && + (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad); +} + +/* takePicture arguments: + * INDEX ARGUMENT + * 0 quality + * 1 destination type + * 2 source type + * 3 targetWidth + * 4 targetHeight + * 5 encodingType + * 6 mediaType + * 7 allowsEdit + * 8 correctOrientation + * 9 saveToPhotoAlbum + */ +- (void)takePicture:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSArray* arguments = command.arguments; + + self.hasPendingOperation = NO; + + NSString* sourceTypeString = [arguments objectAtIndex:2]; + UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; // default + if (sourceTypeString != nil) { + sourceType = (UIImagePickerControllerSourceType)[sourceTypeString intValue]; + } + + bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType]; + if (!hasCamera) { + NSLog(@"Camera.getPicture: source type %d not available.", sourceType); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + + bool allowEdit = [[arguments objectAtIndex:7] boolValue]; + NSNumber* targetWidth = [arguments objectAtIndex:3]; + NSNumber* targetHeight = [arguments objectAtIndex:4]; + NSNumber* mediaValue = [arguments objectAtIndex:6]; + CDVMediaType mediaType = (mediaValue) ? [mediaValue intValue] : MediaTypePicture; + + CGSize targetSize = CGSizeMake(0, 0); + if ((targetWidth != nil) && (targetHeight != nil)) { + targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]); + } + + CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init]; + self.pickerController = cameraPicker; + + cameraPicker.delegate = self; + cameraPicker.sourceType = sourceType; + cameraPicker.allowsEditing = allowEdit; // THIS IS ALL IT TAKES FOR CROPPING - jm + cameraPicker.callbackId = callbackId; + cameraPicker.targetSize = targetSize; + cameraPicker.cropToSize = NO; + // we need to capture this state for memory warnings that dealloc this object + cameraPicker.webView = self.webView; + cameraPicker.popoverSupported = [self popoverSupported]; + + cameraPicker.correctOrientation = [[arguments objectAtIndex:8] boolValue]; + cameraPicker.saveToPhotoAlbum = [[arguments objectAtIndex:9] boolValue]; + + cameraPicker.encodingType = ([arguments objectAtIndex:5]) ? [[arguments objectAtIndex:5] intValue] : EncodingTypeJPEG; + + cameraPicker.quality = ([arguments objectAtIndex:0]) ? [[arguments objectAtIndex:0] intValue] : 50; + cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri; + + if (sourceType == UIImagePickerControllerSourceTypeCamera) { + // we only allow taking pictures (no video) in this api + cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil]; + } else if (mediaType == MediaTypeAll) { + cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType]; + } else { + 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]; + } + int x = 0; + int y = 32; + int width = 320; + int height = 480; + UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny; + NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil]; + if (options) { + x = [options integerValueForKey:@"x" defaultValue:0]; + y = [options integerValueForKey:@"y" defaultValue:32]; + width = [options integerValueForKey:@"width" defaultValue:320]; + height = [options integerValueForKey:@"height" defaultValue:480]; + arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny]; + if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) { + arrowDirection = UIPopoverArrowDirectionAny; + } + } + + cameraPicker.popoverController.delegate = self; + [cameraPicker.popoverController presentPopoverFromRect:CGRectMake(x, y, width, height) + inView:[self.webView superview] + permittedArrowDirections:arrowDirection + animated:YES]; + } else { + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:cameraPicker animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:cameraPicker animated:YES]; + } + } + self.hasPendingOperation = YES; +} + +- (void)cleanup:(CDVInvokedUrlCommand*)command +{ + // empty the tmp directory + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* err = nil; + BOOL hasErrors = NO; + + // clear contents of NSTemporaryDirectory + NSString* tempDirectoryPath = NSTemporaryDirectory(); + NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath]; + NSString* fileName = nil; + BOOL result; + + while ((fileName = [directoryEnumerator nextObject])) { + // only delete the files we created + if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) { + continue; + } + NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName]; + result = [fileMgr removeItemAtPath:filePath error:&err]; + if (!result && err) { + NSLog(@"Failed to delete: %@ (error: %@)", filePath, err); + hasErrors = YES; + } + } + + CDVPluginResult* pluginResult; + if (hasErrors) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)popoverControllerDidDismissPopover:(id)popoverController +{ + // [ self imagePickerControllerDidCancel:self.pickerController ]; ' + UIPopoverController* pc = (UIPopoverController*)popoverController; + + [pc dismissPopoverAnimated:YES]; + pc.delegate = nil; + if (self.pickerController && self.pickerController.callbackId && self.pickerController.popoverController) { + self.pickerController.popoverController = nil; + NSString* callbackId = self.pickerController.callbackId; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + self.hasPendingOperation = NO; +} + +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info +{ + CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker; + + if (cameraPicker.popoverSupported && (cameraPicker.popoverController != nil)) { + [cameraPicker.popoverController dismissPopoverAnimated:YES]; + cameraPicker.popoverController.delegate = nil; + cameraPicker.popoverController = nil; + } else { + if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) { + [[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES]; + } else { + [[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES]; + } + } + + CDVPluginResult* result = nil; + + NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType]; + // IMAGE TYPE + if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) { + // get the image + UIImage* image = nil; + if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) { + image = [info objectForKey:UIImagePickerControllerEditedImage]; + } else { + image = [info objectForKey:UIImagePickerControllerOriginalImage]; + } + + if (cameraPicker.saveToPhotoAlbum) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + } + + if (cameraPicker.correctOrientation) { + image = [self imageCorrectedForCaptureOrientation:image]; + } + + UIImage* scaledImage = nil; + + if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) { + // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping + if (cameraPicker.cropToSize) { + scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize]; + } else { + scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize]; + } + } + + NSData* data = nil; + + if (cameraPicker.encodingType == EncodingTypePNG) { + data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage); + } else { + data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f); + } + + if (cameraPicker.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++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"]; + } while ([fileMgr fileExistsAtPath:filePath]); + + // save file + if (![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:[data base64EncodedString]]; + } + } + // NOT IMAGE TYPE (MOVIE) + else { + NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath]; + } + + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId]; + } + + self.hasPendingOperation = NO; + self.pickerController = nil; +} + +// older api calls newer didFinishPickingMediaWithInfo +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo +{ + NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage]; + + [self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker +{ + CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker; + + if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) { + [[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES]; + } else { + [[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES]; + } + // popoverControllerDidDismissPopover:(id)popoverController is called if popover is cancelled + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM + [self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId]; + + self.hasPendingOperation = NO; + self.pickerController = nil; +} + +- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize +{ + UIImage* sourceImage = anImage; + UIImage* newImage = nil; + CGSize imageSize = sourceImage.size; + CGFloat width = imageSize.width; + CGFloat height = imageSize.height; + CGFloat targetWidth = targetSize.width; + CGFloat targetHeight = targetSize.height; + CGFloat scaleFactor = 0.0; + CGFloat scaledWidth = targetWidth; + CGFloat scaledHeight = targetHeight; + CGPoint thumbnailPoint = CGPointMake(0.0, 0.0); + + if (CGSizeEqualToSize(imageSize, targetSize) == NO) { + CGFloat widthFactor = targetWidth / width; + CGFloat heightFactor = targetHeight / height; + + if (widthFactor > heightFactor) { + scaleFactor = widthFactor; // scale to fit height + } else { + scaleFactor = heightFactor; // scale to fit width + } + scaledWidth = width * scaleFactor; + scaledHeight = height * scaleFactor; + + // center the image + if (widthFactor > heightFactor) { + thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5; + } else if (widthFactor < heightFactor) { + thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5; + } + } + + UIGraphicsBeginImageContext(targetSize); // this will crop + + CGRect thumbnailRect = CGRectZero; + thumbnailRect.origin = thumbnailPoint; + thumbnailRect.size.width = scaledWidth; + thumbnailRect.size.height = scaledHeight; + + [sourceImage drawInRect:thumbnailRect]; + + newImage = UIGraphicsGetImageFromCurrentImageContext(); + if (newImage == nil) { + NSLog(@"could not scale image"); + } + + // pop the context to get back to the default + UIGraphicsEndImageContext(); + return newImage; +} + +- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage +{ + float rotation_radians = 0; + bool perpendicular = false; + + switch ([anImage imageOrientation]) { + case UIImageOrientationUp : + rotation_radians = 0.0; + break; + + case UIImageOrientationDown : + rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math + break; + + case UIImageOrientationRight: + rotation_radians = M_PI_2; + perpendicular = true; + break; + + case UIImageOrientationLeft: + rotation_radians = -M_PI_2; + perpendicular = true; + break; + + default: + break; + } + + UIGraphicsBeginImageContext(CGSizeMake(anImage.size.width, anImage.size.height)); + CGContextRef context = UIGraphicsGetCurrentContext(); + + // Rotate around the center point + CGContextTranslateCTM(context, anImage.size.width / 2, anImage.size.height / 2); + CGContextRotateCTM(context, rotation_radians); + + CGContextScaleCTM(context, 1.0, -1.0); + float width = perpendicular ? anImage.size.height : anImage.size.width; + float height = perpendicular ? anImage.size.width : anImage.size.height; + CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [anImage CGImage]); + + // Move the origin back since the rotation might've change it (if its 90 degrees) + if (perpendicular) { + CGContextTranslateCTM(context, -anImage.size.height / 2, -anImage.size.width / 2); + } + + UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize +{ + UIImage* sourceImage = anImage; + UIImage* newImage = nil; + CGSize imageSize = sourceImage.size; + CGFloat width = imageSize.width; + CGFloat height = imageSize.height; + CGFloat targetWidth = frameSize.width; + CGFloat targetHeight = frameSize.height; + CGFloat scaleFactor = 0.0; + CGSize scaledSize = frameSize; + + if (CGSizeEqualToSize(imageSize, frameSize) == NO) { + CGFloat widthFactor = targetWidth / width; + CGFloat heightFactor = targetHeight / height; + + // opposite comparison to imageByScalingAndCroppingForSize in order to contain the image within the given bounds + if (widthFactor > heightFactor) { + scaleFactor = heightFactor; // scale to fit height + } else { + scaleFactor = widthFactor; // scale to fit width + } + scaledSize = CGSizeMake(MIN(width * scaleFactor, targetWidth), MIN(height * scaleFactor, targetHeight)); + } + + UIGraphicsBeginImageContext(scaledSize); // this will resize + + [sourceImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)]; + + newImage = UIGraphicsGetImageFromCurrentImageContext(); + if (newImage == nil) { + NSLog(@"could not scale image"); + } + + // pop the context to get back to the default + UIGraphicsEndImageContext(); + return newImage; +} + +- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url +{ + self.hasPendingOperation = YES; + + NSString* boundary = @"----BOUNDARY_IS_I"; + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; + [req setHTTPMethod:@"POST"]; + + NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + [req setValue:contentType forHTTPHeaderField:@"Content-type"]; + + NSData* imageData = UIImagePNGRepresentation(anImage); + + // adding the body + NSMutableData* postBody = [NSMutableData data]; + + // 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:imageData]; + + // // second parameter information + // [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + // [postBody appendData:[@"Content-Disposition: form-data; name=\"some_other_name\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + // [postBody appendData:[@"some_other_value" dataUsingEncoding:NSUTF8StringEncoding]]; + // [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + [req setHTTPBody:postBody]; + + NSURLResponse* response; + NSError* error; + [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]; + + // NSData* result = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]; + // NSString * resultStr = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease]; + + self.hasPendingOperation = NO; +} + +@end + +@implementation CDVCameraPicker + +@synthesize quality, postUrl; +@synthesize returnType; +@synthesize callbackId; +@synthesize popoverController; +@synthesize targetSize; +@synthesize correctOrientation; +@synthesize saveToPhotoAlbum; +@synthesize encodingType; +@synthesize cropToSize; +@synthesize webView; +@synthesize popoverSupported; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCapture.h b/iPhone/CordovaLib/Classes/CDVCapture.h new file mode 100755 index 0000000..afb82b4 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCapture.h @@ -0,0 +1,118 @@ +/* + 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> +#import <MobileCoreServices/MobileCoreServices.h> +#import <AVFoundation/AVFoundation.h> +#import "CDVPlugin.h" +#import "CDVFile.h" + +enum CDVCaptureError { + CAPTURE_INTERNAL_ERR = 0, + CAPTURE_APPLICATION_BUSY = 1, + CAPTURE_INVALID_ARGUMENT = 2, + CAPTURE_NO_MEDIA_FILES = 3, + CAPTURE_NOT_SUPPORTED = 20 +}; +typedef NSUInteger CDVCaptureError; + +@interface CDVImagePicker : UIImagePickerController +{ + NSString* callbackid; + NSInteger quality; + NSString* mimeType; +} +@property (assign) NSInteger quality; +@property (copy) NSString* callbackId; +@property (copy) NSString* mimeType; + +@end + +@interface CDVCapture : CDVPlugin <UIImagePickerControllerDelegate, UINavigationControllerDelegate> +{ + CDVImagePicker* pickerController; + BOOL inUse; +} +@property BOOL inUse; +- (void)captureAudio:(CDVInvokedUrlCommand*)command; +- (void)captureImage:(CDVInvokedUrlCommand*)command; +- (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCallbackId:(NSString*)callbackId; +- (void)captureVideo:(CDVInvokedUrlCommand*)command; +- (CDVPluginResult*)processVideo:(NSString*)moviePath forCallbackId:(NSString*)callbackId; +- (void)getMediaModes:(CDVInvokedUrlCommand*)command; +- (void)getFormatData:(CDVInvokedUrlCommand*)command; +- (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString*)type; +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info; +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; +- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker; + +@end + +@interface CDVAudioNavigationController : UINavigationController + +@end + +/* AudioRecorderViewController is used to create a simple view for audio recording. + * It is created from [Capture captureAudio]. It creates a very simple interface for + * recording by presenting just a record/stop button and a Done button to close the view. + * The recording time is displayed and recording for a specified duration is supported. When duration + * is specified there is no UI to the user - recording just stops when the specified + * duration is reached. The UI has been minimized to avoid localization. + */ +@interface CDVAudioRecorderViewController : UIViewController <AVAudioRecorderDelegate> +{ + CDVCaptureError errorCode; + NSString* callbackId; + NSNumber* duration; + CDVCapture* captureCommand; + UIBarButtonItem* doneButton; + UIView* recordingView; + UIButton* recordButton; + UIImage* recordImage; + UIImage* stopRecordImage; + UILabel* timerLabel; + AVAudioRecorder* avRecorder; + AVAudioSession* avSession; + CDVPluginResult* pluginResult; + NSTimer* timer; + BOOL isTimed; +} +@property (nonatomic) CDVCaptureError errorCode; +@property (nonatomic, copy) NSString* callbackId; +@property (nonatomic, copy) NSNumber* duration; +@property (nonatomic, strong) CDVCapture* captureCommand; +@property (nonatomic, strong) UIBarButtonItem* doneButton; +@property (nonatomic, strong) UIView* recordingView; +@property (nonatomic, strong) UIButton* recordButton; +@property (nonatomic, strong) UIImage* recordImage; +@property (nonatomic, strong) UIImage* stopRecordImage; +@property (nonatomic, strong) UILabel* timerLabel; +@property (nonatomic, strong) AVAudioRecorder* avRecorder; +@property (nonatomic, strong) AVAudioSession* avSession; +@property (nonatomic, strong) CDVPluginResult* pluginResult; +@property (nonatomic, strong) NSTimer* timer; +@property (nonatomic) BOOL isTimed; + +- (id)initWithCommand:(CDVPlugin*)theCommand duration:(NSNumber*)theDuration callbackId:(NSString*)theCallbackId; +- (void)processButton:(id)sender; +- (void)stopRecordingCleanup; +- (void)dismissAudioView:(id)sender; +- (NSString*)formatTime:(int)interval; +- (void)updateTime; +@end diff --git a/iPhone/CordovaLib/Classes/CDVCapture.m b/iPhone/CordovaLib/Classes/CDVCapture.m new file mode 100755 index 0000000..6d52042 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCapture.m @@ -0,0 +1,850 @@ +/* + 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 "CDVCapture.h" +#import "JSONKit.h" +#import "CDVAvailability.h" +#import "CDVViewController.h" + +#define kW3CMediaFormatHeight @"height" +#define kW3CMediaFormatWidth @"width" +#define kW3CMediaFormatCodecs @"codecs" +#define kW3CMediaFormatBitrate @"bitrate" +#define kW3CMediaFormatDuration @"duration" +#define kW3CMediaModeType @"type" + +@implementation CDVImagePicker + +@synthesize quality; +@synthesize callbackId; +@synthesize mimeType; + +- (uint64_t)accessibilityTraits +{ + NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; + + if (([systemVersion compare:@"4.0" options:NSNumericSearch] != NSOrderedAscending)) { // this means system version is not less than 4.0 + return UIAccessibilityTraitStartsMediaSession; + } + + return UIAccessibilityTraitNone; +} + +@end + +@implementation CDVCapture +@synthesize inUse; + +- (id)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVCapture*)[super initWithWebView:theWebView]; + if (self) { + self.inUse = NO; + } + return self; +} + +- (void)captureAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0]; + + if ([options isKindOfClass:[NSNull class]]) { + options = [NSDictionary dictionary]; + } + + NSNumber* duration = [options objectForKey:@"duration"]; + // the default value of duration is 0 so use nil (no duration) if default value + if (duration) { + duration = [duration doubleValue] == 0 ? nil : duration; + } + CDVPluginResult* result = nil; + + if (NSClassFromString(@"AVAudioRecorder") == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED]; + } else if (self.inUse == YES) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_APPLICATION_BUSY]; + } else { + // all the work occurs here + CDVAudioRecorderViewController* audioViewController = [[CDVAudioRecorderViewController alloc] initWithCommand:self duration:duration callbackId:callbackId]; + + // Now create a nav controller and display the view... + CDVAudioNavigationController* navController = [[CDVAudioNavigationController alloc] initWithRootViewController:audioViewController]; + + self.inUse = YES; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:navController animated:YES]; + } + } + + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +- (void)captureImage:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0]; + + 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 + // can support mode in OS + + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + NSLog(@"Capture.imageCapture: camera not available."); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + if (pickerController == nil) { + pickerController = [[CDVImagePicker alloc] init]; + } + + pickerController.delegate = self; + pickerController.sourceType = UIImagePickerControllerSourceTypeCamera; + pickerController.allowsEditing = NO; + if ([pickerController respondsToSelector:@selector(mediaTypes)]) { + // iOS 3.0 + pickerController.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil]; + } + + /*if ([pickerController respondsToSelector:@selector(cameraCaptureMode)]){ + // iOS 4.0 + pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto; + pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear; + pickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto; + }*/ + // CDVImagePicker specific property + pickerController.callbackId = callbackId; + pickerController.mimeType = mode; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:pickerController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:pickerController animated:YES]; + } + } +} + +/* Process a still image from the camera. + * IN: + * UIImage* image - the UIImage data returned from the camera + * NSString* callbackId + */ +- (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCallbackId:(NSString*)callbackId +{ + CDVPluginResult* result = nil; + + // save the image to photo album + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + + NSData* data = nil; + if (mimeType && [mimeType isEqualToString:@"image/png"]) { + data = UIImagePNGRepresentation(image); + } else { + data = UIImageJPEGRepresentation(image, 0.5); + } + + // write to temp directory and return URI + NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSError* err = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + + // generate unique file name + NSString* filePath; + int i = 1; + do { + filePath = [NSString stringWithFormat:@"%@/photo_%03d.jpg", docsPath, i++]; + } while ([fileMgr fileExistsAtPath:filePath]); + + if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR]; + if (err) { + NSLog(@"Error saving image: %@", [err localizedDescription]); + } + } else { + // create MediaFile object + + NSDictionary* fileDict = [self getMediaDictionaryFromPath:filePath ofType:mimeType]; + NSArray* fileArray = [NSArray arrayWithObject:fileDict]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray]; + } + + return result; +} + +- (void)captureVideo:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0]; + + if ([options isKindOfClass:[NSNull class]]) { + options = [NSDictionary dictionary]; + } + + // options could contain limit, duration and mode, only duration is supported (but is not due to apple bug) + // taking more than one video (limit) is only supported if provide own controls via cameraOverlayView property + // NSNumber* duration = [options objectForKey:@"duration"]; + NSString* mediaType = nil; + + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + // there is a camera, it is available, make sure it can do movies + pickerController = [[CDVImagePicker alloc] init]; + + NSArray* types = nil; + if ([UIImagePickerController respondsToSelector:@selector(availableMediaTypesForSourceType:)]) { + types = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; + // NSLog(@"MediaTypes: %@", [types description]); + + if ([types containsObject:(NSString*)kUTTypeMovie]) { + mediaType = (NSString*)kUTTypeMovie; + } else if ([types containsObject:(NSString*)kUTTypeVideo]) { + mediaType = (NSString*)kUTTypeVideo; + } + } + } + if (!mediaType) { + // don't have video camera return error + NSLog(@"Capture.captureVideo: video mode not available."); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NOT_SUPPORTED]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + pickerController = nil; + } else { + pickerController.delegate = self; + pickerController.sourceType = UIImagePickerControllerSourceTypeCamera; + pickerController.allowsEditing = NO; + // iOS 3.0 + pickerController.mediaTypes = [NSArray arrayWithObjects:mediaType, nil]; + + /*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)]) { + pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; + // pickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; + // pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear; + // pickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto; + } + // CDVImagePicker specific property + pickerController.callbackId = callbackId; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:pickerController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:pickerController animated:YES]; + } + } +} + +- (CDVPluginResult*)processVideo:(NSString*)moviePath forCallbackId:(NSString*)callbackId +{ + // save the movie to photo album (only avail as of iOS 3.1) + + /* don't need, it should automatically get saved + NSLog(@"can save %@: %d ?", moviePath, UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath)); + if (&UIVideoAtPathIsCompatibleWithSavedPhotosAlbum != NULL && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath) == YES) { + NSLog(@"try to save movie"); + UISaveVideoAtPathToSavedPhotosAlbum(moviePath, nil, nil, nil); + NSLog(@"finished saving movie"); + }*/ + // create MediaFile object + NSDictionary* fileDict = [self getMediaDictionaryFromPath:moviePath ofType:nil]; + NSArray* fileArray = [NSArray arrayWithObject:fileDict]; + + return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray]; +} + +- (void)getMediaModes:(CDVInvokedUrlCommand*)command +{ + // NSString* callbackId = [arguments objectAtIndex:0]; + // NSMutableDictionary* imageModes = nil; + NSArray* imageArray = nil; + NSArray* movieArray = nil; + NSArray* audioArray = nil; + + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + // there is a camera, find the modes + // can get image/jpeg or image/png from camera + + /* can't find a way to get the default height and width and other info + * for images/movies taken with UIImagePickerController + */ + NSDictionary* jpg = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:0], kW3CMediaFormatHeight, + [NSNumber numberWithInt:0], kW3CMediaFormatWidth, + @"image/jpeg", kW3CMediaModeType, + nil]; + NSDictionary* png = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:0], kW3CMediaFormatHeight, + [NSNumber numberWithInt:0], kW3CMediaFormatWidth, + @"image/png", kW3CMediaModeType, + nil]; + imageArray = [NSArray arrayWithObjects:jpg, png, nil]; + + if ([UIImagePickerController respondsToSelector:@selector(availableMediaTypesForSourceType:)]) { + NSArray* types = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; + + if ([types containsObject:(NSString*)kUTTypeMovie]) { + NSDictionary* mov = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:0], kW3CMediaFormatHeight, + [NSNumber numberWithInt:0], kW3CMediaFormatWidth, + @"video/quicktime", kW3CMediaModeType, + nil]; + movieArray = [NSArray arrayWithObject:mov]; + } + } + } + NSDictionary* modes = [NSDictionary dictionaryWithObjectsAndKeys: + imageArray ? (NSObject*) imageArray:[NSNull null], @"image", + movieArray ? (NSObject*) movieArray:[NSNull null], @"video", + audioArray ? (NSObject*) audioArray:[NSNull null], @"audio", + nil]; + NSString* jsString = [NSString stringWithFormat:@"navigator.device.capture.setSupportedModes(%@);", [modes cdvjk_JSONString]]; + [self.commandDelegate evalJs:jsString]; +} + +- (void)getFormatData:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + // existence of fullPath checked on JS side + NSString* fullPath = [command.arguments objectAtIndex:0]; + // mimeType could be null + NSString* mimeType = nil; + + if ([command.arguments count] > 1) { + mimeType = [command.arguments objectAtIndex:1]; + } + BOOL bError = NO; + CDVCaptureError errorCode = CAPTURE_INTERNAL_ERR; + CDVPluginResult* result = nil; + + if (!mimeType || [mimeType isKindOfClass:[NSNull class]]) { + // try to determine mime type if not provided + id command = [self.commandDelegate getCommandInstance:@"File"]; + bError = !([command isKindOfClass:[CDVFile class]]); + if (!bError) { + CDVFile* cdvFile = (CDVFile*)command; + mimeType = [cdvFile getMimeTypeFromPath:fullPath]; + if (!mimeType) { + // can't do much without mimeType, return error + bError = YES; + errorCode = CAPTURE_INVALID_ARGUMENT; + } + } + } + if (!bError) { + // create and initialize return dictionary + NSMutableDictionary* formatData = [NSMutableDictionary dictionaryWithCapacity:5]; + [formatData setObject:[NSNull null] forKey:kW3CMediaFormatCodecs]; + [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatBitrate]; + [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatHeight]; + [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatWidth]; + [formatData setObject:[NSNumber numberWithInt:0] forKey:kW3CMediaFormatDuration]; + + if ([mimeType rangeOfString:@"image/"].location != NSNotFound) { + UIImage* image = [UIImage imageWithContentsOfFile:fullPath]; + if (image) { + CGSize imgSize = [image size]; + [formatData setObject:[NSNumber numberWithInteger:imgSize.width] forKey:kW3CMediaFormatWidth]; + [formatData setObject:[NSNumber numberWithInteger:imgSize.height] forKey:kW3CMediaFormatHeight]; + } + } else if (([mimeType rangeOfString:@"video/"].location != NSNotFound) && (NSClassFromString(@"AVURLAsset") != nil)) { + NSURL* movieURL = [NSURL fileURLWithPath:fullPath]; + AVURLAsset* movieAsset = [[AVURLAsset alloc] initWithURL:movieURL options:nil]; + CMTime duration = [movieAsset duration]; + [formatData setObject:[NSNumber numberWithFloat:CMTimeGetSeconds(duration)] forKey:kW3CMediaFormatDuration]; + + NSArray* allVideoTracks = [movieAsset tracksWithMediaType:AVMediaTypeVideo]; + if ([allVideoTracks count] > 0) { + AVAssetTrack* track = [[movieAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; + CGSize size = [track naturalSize]; + + [formatData setObject:[NSNumber numberWithFloat:size.height] forKey:kW3CMediaFormatHeight]; + [formatData setObject:[NSNumber numberWithFloat:size.width] forKey:kW3CMediaFormatWidth]; + // not sure how to get codecs or bitrate??? + // AVMetadataItem + // AudioFile + } else { + NSLog(@"No video tracks found for %@", fullPath); + } + } else if ([mimeType rangeOfString:@"audio/"].location != NSNotFound) { + if (NSClassFromString(@"AVAudioPlayer") != nil) { + NSURL* fileURL = [NSURL fileURLWithPath:fullPath]; + NSError* err = nil; + + AVAudioPlayer* avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&err]; + if (!err) { + // get the data + [formatData setObject:[NSNumber numberWithDouble:[avPlayer duration]] forKey:kW3CMediaFormatDuration]; + if ([avPlayer respondsToSelector:@selector(settings)]) { + NSDictionary* info = [avPlayer settings]; + NSNumber* bitRate = [info objectForKey:AVEncoderBitRateKey]; + if (bitRate) { + [formatData setObject:bitRate forKey:kW3CMediaFormatBitrate]; + } + } + } // else leave data init'ed to 0 + } + } + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:formatData]; + // NSLog(@"getFormatData: %@", [formatData description]); + } + if (bError) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errorCode]; + } + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +- (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString*)type +{ + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSMutableDictionary* fileDict = [NSMutableDictionary dictionaryWithCapacity:5]; + + [fileDict setObject:[fullPath lastPathComponent] forKey:@"name"]; + [fileDict setObject:fullPath forKey:@"fullPath"]; + // determine type + if (!type) { + id command = [self.commandDelegate getCommandInstance:@"File"]; + if ([command isKindOfClass:[CDVFile class]]) { + CDVFile* cdvFile = (CDVFile*)command; + NSString* mimeType = [cdvFile getMimeTypeFromPath:fullPath]; + [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType:[NSNull null]) forKey:@"type"]; + } + } + NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:nil]; + [fileDict setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; + NSDate* modDate = [fileAttrs fileModificationDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; + [fileDict setObject:msDate forKey:@"lastModifiedDate"]; + + return fileDict; +} + +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo +{ + // older api calls new one + [self imagePickerController:picker didFinishPickingMediaWithInfo:editingInfo]; +} + +/* Called when image/movie is finished recording. + * Calls success or error code as appropriate + * if successful, result contains an array (with just one entry since can only get one image unless build own camera UI) of MediaFile object representing the image + * name + * fullPath + * type + * lastModifiedDate + * size + */ +- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info +{ + CDVImagePicker* cameraPicker = (CDVImagePicker*)picker; + NSString* callbackId = cameraPicker.callbackId; + + if ([picker respondsToSelector:@selector(presentingViewController)]) { + [[picker presentingViewController] dismissModalViewControllerAnimated:YES]; + } else { + [[picker parentViewController] dismissModalViewControllerAnimated:YES]; + } + + CDVPluginResult* result = nil; + + UIImage* image = nil; + NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType]; + if (!mediaType || [mediaType isEqualToString:(NSString*)kUTTypeImage]) { + // mediaType is nil then only option is UIImagePickerControllerOriginalImage + if ([UIImagePickerController respondsToSelector:@selector(allowsEditing)] && + (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage])) { + image = [info objectForKey:UIImagePickerControllerEditedImage]; + } else { + image = [info objectForKey:UIImagePickerControllerOriginalImage]; + } + } + if (image != nil) { + // mediaType was image + result = [self processImage:image type:cameraPicker.mimeType forCallbackId:callbackId]; + } else if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]) { + // process video + NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] path]; + if (moviePath) { + result = [self processVideo:moviePath forCallbackId:callbackId]; + } + } + if (!result) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_INTERNAL_ERR]; + } + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + pickerController = nil; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker +{ + CDVImagePicker* cameraPicker = (CDVImagePicker*)picker; + NSString* callbackId = cameraPicker.callbackId; + + if ([picker respondsToSelector:@selector(presentingViewController)]) { + [[picker presentingViewController] dismissModalViewControllerAnimated:YES]; + } else { + [[picker parentViewController] dismissModalViewControllerAnimated:YES]; + } + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_NO_MEDIA_FILES]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + pickerController = nil; +} + +@end + +@implementation CDVAudioNavigationController + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + - (NSUInteger)supportedInterfaceOrientations + { + // delegate to CVDAudioRecorderViewController + return [self.topViewController supportedInterfaceOrientations]; + } + +#endif + +@end + +@implementation CDVAudioRecorderViewController +@synthesize errorCode, callbackId, duration, captureCommand, doneButton, recordingView, recordButton, recordImage, stopRecordImage, timerLabel, avRecorder, avSession, pluginResult, timer, isTimed; + +- (NSString*)resolveImageResource:(NSString*)resource +{ + NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; + BOOL isLessThaniOS4 = ([systemVersion compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending); + + // the iPad image (nor retina) differentiation code was not in 3.x, and we have to explicitly set the path + // if user wants iPhone only app to run on iPad they must remove *~ipad.* images from capture.bundle + if (isLessThaniOS4) { + NSString* iPadResource = [NSString stringWithFormat:@"%@~ipad.png", resource]; + if (CDV_IsIPad() && [UIImage imageNamed:iPadResource]) { + return iPadResource; + } else { + return [NSString stringWithFormat:@"%@.png", resource]; + } + } + + return resource; +} + +- (id)initWithCommand:(CDVCapture*)theCommand duration:(NSNumber*)theDuration callbackId:(NSString*)theCallbackId +{ + if ((self = [super init])) { + self.captureCommand = theCommand; + self.duration = theDuration; + self.callbackId = theCallbackId; + self.errorCode = CAPTURE_NO_MEDIA_FILES; + self.isTimed = self.duration != nil; + + return self; + } + + return nil; +} + +- (void)loadView +{ + // create view and display + CGRect viewRect = [[UIScreen mainScreen] applicationFrame]; + UIView* tmp = [[UIView alloc] initWithFrame:viewRect]; + + // make backgrounds + NSString* microphoneResource = @"Capture.bundle/microphone"; + + if (CDV_IsIPhone5()) { + microphoneResource = @"Capture.bundle/microphone-568h"; + } + + UIImage* microphone = [UIImage imageNamed:[self resolveImageResource:microphoneResource]]; + UIView* microphoneView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, microphone.size.height)]; + [microphoneView setBackgroundColor:[UIColor colorWithPatternImage:microphone]]; + [microphoneView setUserInteractionEnabled:NO]; + [microphoneView setIsAccessibilityElement:NO]; + [tmp addSubview:microphoneView]; + + // add bottom bar view + UIImage* grayBkg = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/controls_bg"]]; + UIView* controls = [[UIView alloc] initWithFrame:CGRectMake(0, microphone.size.height, viewRect.size.width, grayBkg.size.height)]; + [controls setBackgroundColor:[UIColor colorWithPatternImage:grayBkg]]; + [controls setUserInteractionEnabled:NO]; + [controls setIsAccessibilityElement:NO]; + [tmp addSubview:controls]; + + // make red recording background view + UIImage* recordingBkg = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/recording_bg"]]; + UIColor* background = [UIColor colorWithPatternImage:recordingBkg]; + self.recordingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, recordingBkg.size.height)]; + [self.recordingView setBackgroundColor:background]; + [self.recordingView setHidden:YES]; + [self.recordingView setUserInteractionEnabled:NO]; + [self.recordingView setIsAccessibilityElement:NO]; + [tmp addSubview:self.recordingView]; + + // add label + self.timerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, recordingBkg.size.height)]; + // timerLabel.autoresizingMask = reSizeMask; + [self.timerLabel setBackgroundColor:[UIColor clearColor]]; + [self.timerLabel setTextColor:[UIColor whiteColor]]; + [self.timerLabel setTextAlignment:UITextAlignmentCenter]; + [self.timerLabel setText:@"0:00"]; + [self.timerLabel setAccessibilityHint:NSLocalizedString(@"recorded time in minutes and seconds", nil)]; + self.timerLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently; + self.timerLabel.accessibilityTraits &= ~UIAccessibilityTraitStaticText; + [tmp addSubview:self.timerLabel]; + + // Add record button + + self.recordImage = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/record_button"]]; + self.stopRecordImage = [UIImage imageNamed:[self resolveImageResource:@"Capture.bundle/stop_button"]]; + self.recordButton.accessibilityTraits |= [self accessibilityTraits]; + self.recordButton = [[UIButton alloc] initWithFrame:CGRectMake((viewRect.size.width - recordImage.size.width) / 2, (microphone.size.height + (grayBkg.size.height - recordImage.size.height) / 2), recordImage.size.width, recordImage.size.height)]; + [self.recordButton setAccessibilityLabel:NSLocalizedString(@"toggle audio recording", nil)]; + [self.recordButton setImage:recordImage forState:UIControlStateNormal]; + [self.recordButton addTarget:self action:@selector(processButton:) forControlEvents:UIControlEventTouchUpInside]; + [tmp addSubview:recordButton]; + + // make and add done button to navigation bar + self.doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAudioView:)]; + [self.doneButton setStyle:UIBarButtonItemStyleDone]; + self.navigationItem.rightBarButtonItem = self.doneButton; + + [self setView:tmp]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); + NSError* error = nil; + + if (self.avSession == nil) { + // create audio session + self.avSession = [AVAudioSession sharedInstance]; + if (error) { + // return error if can't create recording audio session + NSLog(@"error creating audio session: %@", [[error userInfo] description]); + self.errorCode = CAPTURE_INTERNAL_ERR; + [self dismissAudioView:nil]; + } + } + + // create file to record to in temporary dir + + NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSError* err = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + + // generate unique file name + NSString* filePath; + int i = 1; + do { + filePath = [NSString stringWithFormat:@"%@/audio_%03d.wav", docsPath, i++]; + } while ([fileMgr fileExistsAtPath:filePath]); + + NSURL* fileURL = [NSURL fileURLWithPath:filePath isDirectory:NO]; + + // create AVAudioPlayer + self.avRecorder = [[AVAudioRecorder alloc] initWithURL:fileURL settings:nil error:&err]; + if (err) { + NSLog(@"Failed to initialize AVAudioRecorder: %@\n", [err localizedDescription]); + self.avRecorder = nil; + // return error + self.errorCode = CAPTURE_INTERNAL_ERR; + [self dismissAudioView:nil]; + } else { + self.avRecorder.delegate = self; + [self.avRecorder prepareToRecord]; + self.recordButton.enabled = YES; + self.doneButton.enabled = YES; + } +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + - (NSUInteger)supportedInterfaceOrientations + { + NSUInteger orientation = UIInterfaceOrientationMaskPortrait; // must support portrait + NSUInteger supported = [captureCommand.viewController supportedInterfaceOrientations]; + + orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown); + return orientation; + } + +#endif + +- (void)viewDidUnload +{ + [self setView:nil]; + [self.captureCommand setInUse:NO]; +} + +- (void)processButton:(id)sender +{ + if (self.avRecorder.recording) { + // stop recording + [self.avRecorder stop]; + self.isTimed = NO; // recording was stopped via button so reset isTimed + // view cleanup will occur in audioRecordingDidFinishRecording + } else { + // begin recording + [self.recordButton setImage:stopRecordImage forState:UIControlStateNormal]; + self.recordButton.accessibilityTraits &= ~[self accessibilityTraits]; + [self.recordingView setHidden:NO]; + NSError* error = nil; + [self.avSession setCategory:AVAudioSessionCategoryRecord error:&error]; + [self.avSession setActive:YES error:&error]; + if (error) { + // can't continue without active audio session + self.errorCode = CAPTURE_INTERNAL_ERR; + [self dismissAudioView:nil]; + } else { + if (self.duration) { + self.isTimed = true; + [self.avRecorder recordForDuration:[duration doubleValue]]; + } else { + [self.avRecorder record]; + } + [self.timerLabel setText:@"0.00"]; + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(updateTime) userInfo:nil repeats:YES]; + self.doneButton.enabled = NO; + } + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + } +} + +/* + * helper method to clean up when stop recording + */ +- (void)stopRecordingCleanup +{ + if (self.avRecorder.recording) { + [self.avRecorder stop]; + } + [self.recordButton setImage:recordImage forState:UIControlStateNormal]; + self.recordButton.accessibilityTraits |= [self accessibilityTraits]; + [self.recordingView setHidden:YES]; + self.doneButton.enabled = YES; + if (self.avSession) { + // deactivate session so sounds can come through + [self.avSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; + [self.avSession setActive:NO error:nil]; + } + if (self.duration && self.isTimed) { + // VoiceOver announcement so user knows timed recording has finished + 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)); + }); + } + } else { + // issue a layout notification change so that VO will reannounce the button label when recording completes + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + } +} + +- (void)dismissAudioView:(id)sender +{ + // called when done button pressed or when error condition to do cleanup and remove view + if ([self.captureCommand.viewController.modalViewController respondsToSelector:@selector(presentingViewController)]) { + [[self.captureCommand.viewController.modalViewController presentingViewController] dismissModalViewControllerAnimated:YES]; + } else { + [[self.captureCommand.viewController.modalViewController parentViewController] dismissModalViewControllerAnimated:YES]; + } + + if (!self.pluginResult) { + // return error + self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:self.errorCode]; + } + + self.avRecorder = nil; + [self.avSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; + [self.avSession setActive:NO error:nil]; + [self.captureCommand setInUse:NO]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); + // return result + [self.captureCommand.commandDelegate sendPluginResult:pluginResult callbackId:callbackId]; +} + +- (void)updateTime +{ + // update the label with the elapsed time + [self.timerLabel setText:[self formatTime:self.avRecorder.currentTime]]; +} + +- (NSString*)formatTime:(int)interval +{ + // is this format universal? + int secs = interval % 60; + int min = interval / 60; + + if (interval < 60) { + return [NSString stringWithFormat:@"0:%02d", interval]; + } else { + return [NSString stringWithFormat:@"%d:%02d", min, secs]; + } +} + +- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag +{ + // may be called when timed audio finishes - need to stop time and reset buttons + [self.timer invalidate]; + [self stopRecordingCleanup]; + + // generate success result + if (flag) { + NSString* filePath = [avRecorder.url path]; + // NSLog(@"filePath: %@", filePath); + NSDictionary* fileDict = [captureCommand getMediaDictionaryFromPath:filePath ofType:@"audio/wav"]; + NSArray* fileArray = [NSArray arrayWithObject:fileDict]; + + self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray]; + } else { + self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR]; + } +} + +- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder*)recorder error:(NSError*)error +{ + [self.timer invalidate]; + [self stopRecordingCleanup]; + + NSLog(@"error recording audio"); + self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageToErrorObject:CAPTURE_INTERNAL_ERR]; + [self dismissAudioView:nil]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h new file mode 100755 index 0000000..4a53f98 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h @@ -0,0 +1,48 @@ +/* + 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 "CDVAvailability.h" +#import "CDVInvokedUrlCommand.h" + +@class CDVPlugin; +@class CDVPluginResult; + +@protocol CDVCommandDelegate <NSObject> + +- (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. +- (BOOL)execute:(CDVInvokedUrlCommand*)command CDV_DEPRECATED(2.2, "Use direct method calls instead."); + +// Sends a plugin result to the JS. This is thread-safe. +- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId; +// Evaluates the given JS. This is thread-safe. +- (void)evalJs:(NSString*)js; +// Can be used to evaluate JS right away instead of scheduling it on the run-loop. +// This is required for dispatch resign and pause events, but should not be used +// without reason. Without the run-loop delay, alerts used in JS callbacks may result +// in dead-lock. This method must be called from the UI thread. +- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop; +// Runs the given block on a background thread using a shared thread-pool. +- (void)runInBackground:(void (^)())block; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.h b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.h new file mode 100755 index 0000000..6735136 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.h @@ -0,0 +1,33 @@ +/* + 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 <UIKit/UIKit.h> +#import "CDVCommandDelegate.h" + +@class CDVViewController; +@class CDVCommandQueue; + +@interface CDVCommandDelegateImpl : NSObject <CDVCommandDelegate>{ + @private + __weak CDVViewController* _viewController; + @protected + __weak CDVCommandQueue* _commandQueue; +} +- (id)initWithViewController:(CDVViewController*)viewController; +@end diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m new file mode 100755 index 0000000..76413bd --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m @@ -0,0 +1,131 @@ +/* + 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 "CDVCommandDelegateImpl.h" + +#import "CDVCommandQueue.h" +#import "CDVPluginResult.h" +#import "CDVViewController.h" + +@implementation CDVCommandDelegateImpl + +- (id)initWithViewController:(CDVViewController*)viewController +{ + self = [super init]; + if (self != nil) { + _viewController = viewController; + _commandQueue = _viewController.commandQueue; + } + return self; +} + +- (NSString*)pathForResource:(NSString*)resourcepath +{ + NSBundle* mainBundle = [NSBundle mainBundle]; + NSMutableArray* directoryParts = [NSMutableArray arrayWithArray:[resourcepath componentsSeparatedByString:@"/"]]; + NSString* filename = [directoryParts lastObject]; + + [directoryParts removeLastObject]; + + NSString* directoryPartsJoined = [directoryParts componentsJoinedByString:@"/"]; + NSString* directoryStr = _viewController.wwwFolderName; + + if ([directoryPartsJoined length] > 0) { + directoryStr = [NSString stringWithFormat:@"%@/%@", _viewController.wwwFolderName, [directoryParts componentsJoinedByString:@"/"]]; + } + + return [mainBundle pathForResource:filename ofType:@"" inDirectory:directoryStr]; +} + +- (void)evalJsHelper2:(NSString*)js +{ + NSString* commandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString:js]; + + [_commandQueue enqueCommandBatch:commandsJSON]; +} + +- (void)evalJsHelper:(NSString*)js +{ + // Cycle the run-loop before executing the JS. + // This works around a bug where sometimes alerts() within callbacks can cause + // dead-lock. + // If the commandQueue is currently executing, then we know that it is safe to + // execute the callback immediately. + // Using (dispatch_get_main_queue()) does *not* fix deadlocks for some reaon, + // but performSelectorOnMainThread: does. + if (![NSThread isMainThread] || !_commandQueue.currentlyExecuting) { + [self performSelectorOnMainThread:@selector(evalJsHelper2:) withObject:js waitUntilDone:NO]; + } else { + [self evalJsHelper2:js]; + } +} + +- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId +{ + int status = [result.status intValue]; + BOOL keepCallback = [result.keepCallback boolValue]; + id message = result.message == nil ? [NSNull null] : result.message; + + // Use an array to encode the message as JSON. + message = [NSArray arrayWithObject:message]; + NSString* encodedMessage = [message cdvjk_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]; + + [self evalJsHelper:js]; +} + +- (void)evalJs:(NSString*)js +{ + [self evalJs:js scheduledOnRunLoop:YES]; +} + +- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop +{ + js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeEvalAndFetch(function(){%@})", js]; + if (scheduledOnRunLoop) { + [self evalJsHelper:js]; + } else { + [self evalJsHelper2:js]; + } +} + +- (BOOL)execute:(CDVInvokedUrlCommand*)command +{ + return [_commandQueue execute:command]; +} + +- (id)getCommandInstance:(NSString*)pluginName +{ + return [_viewController getCommandInstance:pluginName]; +} + +- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className +{ + [_viewController registerPlugin:plugin withClassName:className]; +} + +- (void)runInBackground:(void (^)())block +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCommandQueue.h b/iPhone/CordovaLib/Classes/CDVCommandQueue.h new file mode 100755 index 0000000..ebdf844 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.h @@ -0,0 +1,46 @@ +/* + 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> + +@class CDVInvokedUrlCommand; +@class CDVViewController; + +@interface CDVCommandQueue : NSObject { + @private + NSInteger _lastCommandQueueFlushRequestId; + __weak CDVViewController* _viewController; + NSMutableArray* _queue; + BOOL _currentlyExecuting; +} + +@property (nonatomic, readonly) BOOL currentlyExecuting; + +- (id)initWithViewController:(CDVViewController*)viewController; +- (void)dispose; + +- (void)resetRequestId; +- (void)enqueCommandBatch:(NSString*)batchJSON; + +- (void)maybeFetchCommandsFromJs:(NSNumber*)requestId; +- (void)fetchCommandsFromJs; +- (void)executePending; +- (BOOL)execute:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCommandQueue.m b/iPhone/CordovaLib/Classes/CDVCommandQueue.m new file mode 100755 index 0000000..d88a730 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.m @@ -0,0 +1,156 @@ +/* + 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 <objc/message.h> +#import "CDV.h" +#import "CDVCommandQueue.h" +#import "CDVViewController.h" +#import "CDVCommandDelegateImpl.h" + +@implementation CDVCommandQueue + +@synthesize currentlyExecuting = _currentlyExecuting; + +- (id)initWithViewController:(CDVViewController*)viewController +{ + self = [super init]; + if (self != nil) { + _viewController = viewController; + _queue = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dispose +{ + // TODO(agrieve): Make this a zeroing weak ref once we drop support for 4.3. + _viewController = nil; +} + +- (void)resetRequestId +{ + _lastCommandQueueFlushRequestId = 0; +} + +- (void)enqueCommandBatch:(NSString*)batchJSON +{ + if ([batchJSON length] > 0) { + [_queue addObject:batchJSON]; + [self executePending]; + } +} + +- (void)maybeFetchCommandsFromJs:(NSNumber*)requestId +{ + // Use the request ID to determine if we've already flushed for this request. + // This is required only because the NSURLProtocol enqueues the same request + // multiple times. + if ([requestId integerValue] > _lastCommandQueueFlushRequestId) { + _lastCommandQueueFlushRequestId = [requestId integerValue]; + [self fetchCommandsFromJs]; + } +} + +- (void)fetchCommandsFromJs +{ + // Grab all the queued commands from the JS side. + NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString: + @"cordova.require('cordova/exec').nativeFetchMessages()"]; + + [self enqueCommandBatch:queuedCommandsJSON]; +} + +- (void)executePending +{ + // Make us re-entrant-safe. + if (_currentlyExecuting) { + return; + } + @try { + _currentlyExecuting = YES; + + for (NSUInteger i = 0; i < [_queue count]; ++i) { + // Parse the returned JSON array. + NSArray* commandBatch = [[_queue objectAtIndex:i] cdvjk_mutableObjectFromJSONString]; + + // Iterate over and execute all of the commands. + for (NSArray* jsonEntry in commandBatch) { + CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; + if (![self execute:command]) { +#ifdef DEBUG + NSString* commandJson = [jsonEntry cdvjk_JSONString]; + static NSUInteger maxLogLength = 1024; + NSString* commandString = ([commandJson length] > maxLogLength) ? + [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : + commandJson; + + DLog(@"FAILED pluginJSON = %@", commandString); +#endif + } + } + } + + [_queue removeAllObjects]; + } @finally + { + _currentlyExecuting = NO; + } +} + +- (BOOL)execute:(CDVInvokedUrlCommand*)command +{ + if ((command.className == nil) || (command.methodName == nil)) { + NSLog(@"ERROR: Classname and/or methodName not found for command."); + return NO; + } + + // Fetch an instance of this class + CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className]; + + if (!([obj isKindOfClass:[CDVPlugin class]])) { + NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.", command.className); + return NO; + } + BOOL retVal = YES; + + // Find the proper selector to call. + NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName]; + NSString* methodNameWithDict = [NSString stringWithFormat:@"%@:withDict:", command.methodName]; + SEL normalSelector = NSSelectorFromString(methodName); + SEL legacySelector = NSSelectorFromString(methodNameWithDict); + // Test for the legacy selector first in case they both exist. + if ([obj respondsToSelector:legacySelector]) { + NSMutableArray* arguments = nil; + NSMutableDictionary* dict = nil; + [command legacyArguments:&arguments andDict:&dict]; + // [obj performSelector:legacySelector withObject:arguments withObject:dict]; + objc_msgSend(obj, legacySelector, arguments, dict); + } else if ([obj respondsToSelector:normalSelector]) { + // [obj performSelector:normalSelector withObject:command]; + objc_msgSend(obj, normalSelector, command); + } else { + // There's no method to call, so throw an error. + NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className); + retVal = NO; + } + + return retVal; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.h b/iPhone/CordovaLib/Classes/CDVConfigParser.h new file mode 100755 index 0000000..23cdd01 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.h @@ -0,0 +1,27 @@ +/* + 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. + */ + +@interface CDVConfigParser : NSObject <NSXMLParserDelegate> { +} + +@property (nonatomic, readonly, strong) NSMutableDictionary* pluginsDict; +@property (nonatomic, readonly, strong) NSMutableDictionary* settings; +@property (nonatomic, readonly, strong) NSMutableArray* whitelistHosts; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.m b/iPhone/CordovaLib/Classes/CDVConfigParser.m new file mode 100755 index 0000000..b596c9d --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.m @@ -0,0 +1,61 @@ +/* + 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 "CDVConfigParser.h" + +@interface CDVConfigParser () + +@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginsDict; +@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; +@property (nonatomic, readwrite, strong) NSMutableArray* whitelistHosts; + +@end + +@implementation CDVConfigParser + +@synthesize pluginsDict, settings, whitelistHosts; + +- (id)init +{ + self = [super init]; + if (self != nil) { + self.pluginsDict = [[NSMutableDictionary alloc] initWithCapacity:4]; + self.settings = [[NSMutableDictionary alloc] initWithCapacity:4]; + self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:1]; + } + return self; +} + +- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict +{ + if ([elementName isEqualToString:@"preference"]) { + [settings setObject:[attributeDict objectForKey:@"value"] forKey:[attributeDict objectForKey:@"name"]]; + } else if ([elementName isEqualToString:@"plugin"]) { + [pluginsDict setObject:[attributeDict objectForKey:@"value"] forKey:[attributeDict objectForKey:@"name"]]; + } else if ([elementName isEqualToString:@"access"]) { + [whitelistHosts addObject:[attributeDict objectForKey:@"origin"]]; + } +} + +- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError +{ + NSAssert(NO, @"config.xml parse error line %d col %d", [parser lineNumber], [parser columnNumber]); +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVConnection.h b/iPhone/CordovaLib/Classes/CDVConnection.h new file mode 100755 index 0000000..d3e8c5d --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVConnection.h @@ -0,0 +1,34 @@ +/* + 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> +#import "CDVPlugin.h" +#import "CDVReachability.h" + +@interface CDVConnection : CDVPlugin { + NSString* type; + NSString* _callbackId; + + CDVReachability* internetReach; +} + +@property (copy) NSString* connectionType; +@property (strong) CDVReachability* internetReach; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVConnection.m b/iPhone/CordovaLib/Classes/CDVConnection.m new file mode 100755 index 0000000..3030711 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVConnection.m @@ -0,0 +1,124 @@ +/* + 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 "CDVConnection.h" +#import "CDVReachability.h" + +@interface CDVConnection (PrivateMethods) +- (void)updateOnlineStatus; +- (void)sendPluginResult; +@end + +@implementation CDVConnection + +@synthesize connectionType, internetReach; + +- (void)getConnectionInfo:(CDVInvokedUrlCommand*)command +{ + _callbackId = command.callbackId; + [self sendPluginResult]; +} + +- (void)sendPluginResult +{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:self.connectionType]; + + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:_callbackId]; +} + +- (NSString*)w3cConnectionTypeFor:(CDVReachability*)reachability +{ + NetworkStatus networkStatus = [reachability currentReachabilityStatus]; + + switch (networkStatus) { + case NotReachable: + return @"none"; + + case ReachableViaWWAN: + return @"2g"; // no generic default, so we use the lowest common denominator + + case ReachableViaWiFi: + return @"wifi"; + + default: + return @"unknown"; + } +} + +- (BOOL)isCellularConnection:(NSString*)theConnectionType +{ + return [theConnectionType isEqualToString:@"2g"] || + [theConnectionType isEqualToString:@"3g"] || + [theConnectionType isEqualToString:@"4g"]; +} + +- (void)updateReachability:(CDVReachability*)reachability +{ + if (reachability) { + // check whether the connection type has changed + NSString* newConnectionType = [self w3cConnectionTypeFor:reachability]; + if ([newConnectionType isEqualToString:self.connectionType]) { // the same as before, remove dupes + return; + } else { + self.connectionType = [self w3cConnectionTypeFor:reachability]; + } + } + [self sendPluginResult]; +} + +- (void)updateConnectionType:(NSNotification*)note +{ + CDVReachability* curReach = [note object]; + + if ((curReach != nil) && [curReach isKindOfClass:[CDVReachability class]]) { + [self updateReachability:curReach]; + } +} + +- (void)onPause +{ + [self.internetReach stopNotifier]; +} + +- (void)onResume +{ + [self.internetReach startNotifier]; + [self updateReachability:self.internetReach]; +} + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +{ + self = [super initWithWebView:theWebView]; + if (self) { + self.connectionType = @"none"; + self.internetReach = [CDVReachability reachabilityForInternetConnection]; + self.connectionType = [self w3cConnectionTypeFor:self.internetReach]; + [self.internetReach startNotifier]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateConnectionType:) + name:kReachabilityChangedNotification object:nil]; + if (&UIApplicationDidEnterBackgroundNotification && &UIApplicationWillEnterForegroundNotification) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; + } + } + return self; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVContact.h b/iPhone/CordovaLib/Classes/CDVContact.h new file mode 100755 index 0000000..5187efc --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVContact.h @@ -0,0 +1,136 @@ +/* + 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> +#import <AddressBook/ABAddressBook.h> +#import <AddressBookUI/AddressBookUI.h> + +enum CDVContactError { + UNKNOWN_ERROR = 0, + INVALID_ARGUMENT_ERROR = 1, + TIMEOUT_ERROR = 2, + PENDING_OPERATION_ERROR = 3, + IO_ERROR = 4, + NOT_SUPPORTED_ERROR = 5, + PERMISSION_DENIED_ERROR = 20 +}; +typedef NSUInteger CDVContactError; + +@interface CDVContact : NSObject { + ABRecordRef record; // the ABRecord associated with this contact + NSDictionary* returnFields; // dictionary of fields to return when performing search +} + +@property (nonatomic, assign) ABRecordRef record; +@property (nonatomic, strong) NSDictionary* returnFields; + ++ (NSDictionary*)defaultABtoW3C; ++ (NSDictionary*)defaultW3CtoAB; ++ (NSSet*)defaultW3CtoNull; ++ (NSDictionary*)defaultObjectAndProperties; ++ (NSDictionary*)defaultFields; + ++ (NSDictionary*)calcReturnFields:(NSArray*)fields; +- (id)init; +- (id)initFromABRecord:(ABRecordRef)aRecord; +- (bool)setFromContactDict:(NSDictionary*)aContact asUpdate:(BOOL)bUpdate; + ++ (BOOL)needsConversion:(NSString*)W3Label; ++ (CFStringRef)convertContactTypeToPropertyLabel:(NSString*)label; ++ (NSString*)convertPropertyLabelToContactType:(NSString*)label; ++ (BOOL)isValidW3ContactType:(NSString*)label; +- (bool)setValue:(id)aValue forProperty:(ABPropertyID)aProperty inRecord:(ABRecordRef)aRecord asUpdate:(BOOL)bUpdate; + +- (NSDictionary*)toDictionary:(NSDictionary*)withFields; +- (NSNumber*)getDateAsNumber:(ABPropertyID)datePropId; +- (NSObject*)extractName; +- (NSObject*)extractMultiValue:(NSString*)propertyId; +- (NSObject*)extractAddresses; +- (NSObject*)extractIms; +- (NSObject*)extractOrganizations; +- (NSObject*)extractPhotos; + +- (NSMutableDictionary*)translateW3Dict:(NSDictionary*)dict forProperty:(ABPropertyID)prop; +- (bool)setMultiValueStrings:(NSArray*)fieldArray forProperty:(ABPropertyID)prop inRecord:(ABRecordRef)person asUpdate:(BOOL)bUpdate; +- (bool)setMultiValueDictionary:(NSArray*)array forProperty:(ABPropertyID)prop inRecord:(ABRecordRef)person asUpdate:(BOOL)bUpdate; +- (ABMultiValueRef)allocStringMultiValueFromArray:array; +- (ABMultiValueRef)allocDictMultiValueFromArray:array forProperty:(ABPropertyID)prop; +- (BOOL)foundValue:(NSString*)testValue inFields:(NSDictionary*)searchFields; +- (BOOL)testStringValue:(NSString*)testValue forW3CProperty:(NSString*)property; +- (BOOL)testDateValue:(NSString*)testValue forW3CProperty:(NSString*)property; +- (BOOL)searchContactFields:(NSArray*)fields forMVStringProperty:(ABPropertyID)propId withValue:testValue; +- (BOOL)testMultiValueStrings:(NSString*)testValue forProperty:(ABPropertyID)propId ofType:(NSString*)type; +- (NSArray*)valuesForProperty:(ABPropertyID)propId inRecord:(ABRecordRef)aRecord; +- (NSArray*)labelsForProperty:(ABPropertyID)propId inRecord:(ABRecordRef)aRecord; +- (BOOL)searchContactFields:(NSArray*)fields forMVDictionaryProperty:(ABPropertyID)propId withValue:(NSString*)testValue; + +@end + +// generic ContactField types +#define kW3ContactFieldType @"type" +#define kW3ContactFieldValue @"value" +#define kW3ContactFieldPrimary @"pref" +// Various labels for ContactField types +#define kW3ContactWorkLabel @"work" +#define kW3ContactHomeLabel @"home" +#define kW3ContactOtherLabel @"other" +#define kW3ContactPhoneFaxLabel @"fax" +#define kW3ContactPhoneMobileLabel @"mobile" +#define kW3ContactPhonePagerLabel @"pager" +#define kW3ContactUrlBlog @"blog" +#define kW3ContactUrlProfile @"profile" +#define kW3ContactImAIMLabel @"aim" +#define kW3ContactImICQLabel @"icq" +#define kW3ContactImMSNLabel @"msn" +#define kW3ContactImYahooLabel @"yahoo" +#define kW3ContactFieldId @"id" +// special translation for IM field value and type +#define kW3ContactImType @"type" +#define kW3ContactImValue @"value" + +// Contact object +#define kW3ContactId @"id" +#define kW3ContactName @"name" +#define kW3ContactFormattedName @"formatted" +#define kW3ContactGivenName @"givenName" +#define kW3ContactFamilyName @"familyName" +#define kW3ContactMiddleName @"middleName" +#define kW3ContactHonorificPrefix @"honorificPrefix" +#define kW3ContactHonorificSuffix @"honorificSuffix" +#define kW3ContactDisplayName @"displayName" +#define kW3ContactNickname @"nickname" +#define kW3ContactPhoneNumbers @"phoneNumbers" +#define kW3ContactAddresses @"addresses" +#define kW3ContactAddressFormatted @"formatted" +#define kW3ContactStreetAddress @"streetAddress" +#define kW3ContactLocality @"locality" +#define kW3ContactRegion @"region" +#define kW3ContactPostalCode @"postalCode" +#define kW3ContactCountry @"country" +#define kW3ContactEmails @"emails" +#define kW3ContactIms @"ims" +#define kW3ContactOrganizations @"organizations" +#define kW3ContactOrganizationName @"name" +#define kW3ContactTitle @"title" +#define kW3ContactDepartment @"department" +#define kW3ContactBirthday @"birthday" +#define kW3ContactNote @"note" +#define kW3ContactPhotos @"photos" +#define kW3ContactCategories @"categories" +#define kW3ContactUrls @"urls" diff --git a/iPhone/CordovaLib/Classes/CDVContact.m b/iPhone/CordovaLib/Classes/CDVContact.m new file mode 100755 index 0000000..9389207 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVContact.m @@ -0,0 +1,1729 @@ +/* + 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 "CDVContact.h" +#import "NSDictionary+Extensions.h" + +#define DATE_OR_NULL(dateObj) ((aDate != nil) ? (id)([aDate descriptionWithLocale:[NSLocale currentLocale]]) : (id)([NSNull null])) +#define IS_VALID_VALUE(value) ((value != nil) && (![value isKindOfClass:[NSNull class]])) + +static NSDictionary* org_apache_cordova_contacts_W3CtoAB = nil; +static NSDictionary* org_apache_cordova_contacts_ABtoW3C = nil; +static NSSet* org_apache_cordova_contacts_W3CtoNull = nil; +static NSDictionary* org_apache_cordova_contacts_objectAndProperties = nil; +static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; + +@implementation CDVContact : NSObject + + @synthesize returnFields; + +- (id)init +{ + if ((self = [super init]) != nil) { + ABRecordRef rec = ABPersonCreate(); + self.record = rec; + CFRelease(rec); + } + return self; +} + +- (id)initFromABRecord:(ABRecordRef)aRecord +{ + if ((self = [super init]) != nil) { + self.record = aRecord; + } + return self; +} + +/* synthesize 'record' ourselves to have retain properties for CF types */ + +- (void)setRecord:(ABRecordRef)aRecord +{ + if (record != NULL) { + CFRelease(record); + } + if (aRecord != NULL) { + record = CFRetain(aRecord); + } +} + +- (ABRecordRef)record +{ + return record; +} + +/* Rather than creating getters and setters for each AddressBook (AB) Property, generic methods are used to deal with + * simple properties, MultiValue properties( phone numbers and emails) and MultiValueDictionary properties (Ims and addresses). + * The dictionaries below are used to translate between the W3C identifiers and the AB properties. Using the dictionaries, + * allows looping through sets of properties to extract from or set into the W3C dictionary to/from the ABRecord. + */ + +/* The two following dictionaries translate between W3C properties and AB properties. It currently mixes both + * Properties (kABPersonAddressProperty for example) and Strings (kABPersonAddressStreetKey) so users should be aware of + * what types of values are expected. + * a bit. +*/ ++ (NSDictionary*)defaultABtoW3C +{ + if (org_apache_cordova_contacts_ABtoW3C == nil) { + org_apache_cordova_contacts_ABtoW3C = [NSDictionary dictionaryWithObjectsAndKeys: + kW3ContactNickname, [NSNumber numberWithInt:kABPersonNicknameProperty], + kW3ContactGivenName, [NSNumber numberWithInt:kABPersonFirstNameProperty], + kW3ContactFamilyName, [NSNumber numberWithInt:kABPersonLastNameProperty], + kW3ContactMiddleName, [NSNumber numberWithInt:kABPersonMiddleNameProperty], + kW3ContactHonorificPrefix, [NSNumber numberWithInt:kABPersonPrefixProperty], + kW3ContactHonorificSuffix, [NSNumber numberWithInt:kABPersonSuffixProperty], + kW3ContactPhoneNumbers, [NSNumber numberWithInt:kABPersonPhoneProperty], + kW3ContactAddresses, [NSNumber numberWithInt:kABPersonAddressProperty], + kW3ContactStreetAddress, kABPersonAddressStreetKey, + kW3ContactLocality, kABPersonAddressCityKey, + kW3ContactRegion, kABPersonAddressStateKey, + kW3ContactPostalCode, kABPersonAddressZIPKey, + kW3ContactCountry, kABPersonAddressCountryKey, + kW3ContactEmails, [NSNumber numberWithInt:kABPersonEmailProperty], + kW3ContactIms, [NSNumber numberWithInt:kABPersonInstantMessageProperty], + kW3ContactOrganizations, [NSNumber numberWithInt:kABPersonOrganizationProperty], + kW3ContactOrganizationName, [NSNumber numberWithInt:kABPersonOrganizationProperty], + kW3ContactTitle, [NSNumber numberWithInt:kABPersonJobTitleProperty], + kW3ContactDepartment, [NSNumber numberWithInt:kABPersonDepartmentProperty], + kW3ContactBirthday, [NSNumber numberWithInt:kABPersonBirthdayProperty], + kW3ContactUrls, [NSNumber numberWithInt:kABPersonURLProperty], + kW3ContactNote, [NSNumber numberWithInt:kABPersonNoteProperty], + nil]; + } + + return org_apache_cordova_contacts_ABtoW3C; +} + ++ (NSDictionary*)defaultW3CtoAB +{ + if (org_apache_cordova_contacts_W3CtoAB == nil) { + org_apache_cordova_contacts_W3CtoAB = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:kABPersonNicknameProperty], kW3ContactNickname, + [NSNumber numberWithInt:kABPersonFirstNameProperty], kW3ContactGivenName, + [NSNumber numberWithInt:kABPersonLastNameProperty], kW3ContactFamilyName, + [NSNumber numberWithInt:kABPersonMiddleNameProperty], kW3ContactMiddleName, + [NSNumber numberWithInt:kABPersonPrefixProperty], kW3ContactHonorificPrefix, + [NSNumber numberWithInt:kABPersonSuffixProperty], kW3ContactHonorificSuffix, + [NSNumber numberWithInt:kABPersonPhoneProperty], kW3ContactPhoneNumbers, + [NSNumber numberWithInt:kABPersonAddressProperty], kW3ContactAddresses, + kABPersonAddressStreetKey, kW3ContactStreetAddress, + kABPersonAddressCityKey, kW3ContactLocality, + kABPersonAddressStateKey, kW3ContactRegion, + kABPersonAddressZIPKey, kW3ContactPostalCode, + kABPersonAddressCountryKey, kW3ContactCountry, + [NSNumber numberWithInt:kABPersonEmailProperty], kW3ContactEmails, + [NSNumber numberWithInt:kABPersonInstantMessageProperty], kW3ContactIms, + [NSNumber numberWithInt:kABPersonOrganizationProperty], kW3ContactOrganizations, + [NSNumber numberWithInt:kABPersonJobTitleProperty], kW3ContactTitle, + [NSNumber numberWithInt:kABPersonDepartmentProperty], kW3ContactDepartment, + [NSNumber numberWithInt:kABPersonBirthdayProperty], kW3ContactBirthday, + [NSNumber numberWithInt:kABPersonNoteProperty], kW3ContactNote, + [NSNumber numberWithInt:kABPersonURLProperty], kW3ContactUrls, + kABPersonInstantMessageUsernameKey, kW3ContactImValue, + kABPersonInstantMessageServiceKey, kW3ContactImType, + [NSNull null], kW3ContactFieldType, /* include entries in dictionary to indicate ContactField properties */ + [NSNull null], kW3ContactFieldValue, + [NSNull null], kW3ContactFieldPrimary, + [NSNull null], kW3ContactFieldId, + [NSNumber numberWithInt:kABPersonOrganizationProperty], kW3ContactOrganizationName, /* careful, name is used multiple times*/ + nil]; + } + return org_apache_cordova_contacts_W3CtoAB; +} + ++ (NSSet*)defaultW3CtoNull +{ + // these are values that have no AddressBook Equivalent OR have not been implemented yet + if (org_apache_cordova_contacts_W3CtoNull == nil) { + org_apache_cordova_contacts_W3CtoNull = [NSSet setWithObjects:kW3ContactDisplayName, + kW3ContactCategories, kW3ContactFormattedName, nil]; + } + return org_apache_cordova_contacts_W3CtoNull; +} + +/* + * The objectAndProperties dictionary contains the all of the properties of the W3C Contact Objects specified by the key + * Used in calcReturnFields, and various extract<Property> methods + */ ++ (NSDictionary*)defaultObjectAndProperties +{ + if (org_apache_cordova_contacts_objectAndProperties == nil) { + org_apache_cordova_contacts_objectAndProperties = [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObjects:kW3ContactGivenName, kW3ContactFamilyName, + kW3ContactMiddleName, kW3ContactHonorificPrefix, kW3ContactHonorificSuffix, kW3ContactFormattedName, nil], kW3ContactName, + [NSArray arrayWithObjects:kW3ContactStreetAddress, kW3ContactLocality, kW3ContactRegion, + 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, + [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactPhotos, + [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactUrls, + [NSArray arrayWithObjects:kW3ContactImValue, kW3ContactImType, nil], kW3ContactIms, + nil]; + } + return org_apache_cordova_contacts_objectAndProperties; +} + ++ (NSDictionary*)defaultFields +{ + if (org_apache_cordova_contacts_defaultFields == nil) { + org_apache_cordova_contacts_defaultFields = [NSDictionary dictionaryWithObjectsAndKeys: + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactName], kW3ContactName, + [NSNull null], kW3ContactNickname, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactAddresses], kW3ContactAddresses, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactOrganizations], kW3ContactOrganizations, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactPhoneNumbers], kW3ContactPhoneNumbers, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactEmails], kW3ContactEmails, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactIms], kW3ContactIms, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactPhotos], kW3ContactPhotos, + [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactUrls], kW3ContactUrls, + [NSNull null], kW3ContactBirthday, + [NSNull null], kW3ContactNote, + nil]; + } + return org_apache_cordova_contacts_defaultFields; +} + +/* Translate W3C Contact data into ABRecordRef + * + * New contact information comes in as a NSMutableDictionary. All Null entries in Contact object are set + * as [NSNull null] in the dictionary when translating from the JSON input string of Contact data. However, if + * user did not set a value within a Contact object or sub-object (by not using the object constructor) some data + * may not exist. + * bUpdate = YES indicates this is a save of an existing record + */ +- (bool)setFromContactDict:(NSDictionary*)aContact asUpdate:(BOOL)bUpdate +{ + if (![aContact isKindOfClass:[NSDictionary class]]) { + return FALSE; // can't do anything if no dictionary! + } + + ABRecordRef person = self.record; + bool bSuccess = TRUE; + CFErrorRef error; + + // set name info + // iOS doesn't have displayName - might have to pull parts from it to create name + bool bName = false; + NSDictionary* dict = [aContact valueForKey:kW3ContactName]; + if ([dict isKindOfClass:[NSDictionary class]]) { + bName = true; + 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 + [self setValue:[dict valueForKey:i] forProperty:(ABPropertyID)[(NSNumber*)[[CDVContact defaultW3CtoAB] objectForKey:i] intValue] + inRecord:person asUpdate:bUpdate]; + } + } + } + + id nn = [aContact valueForKey:kW3ContactNickname]; + if (![nn isKindOfClass:[NSNull class]]) { + bName = true; + [self setValue:nn forProperty:kABPersonNicknameProperty inRecord:person asUpdate:bUpdate]; + } + if (!bName) { + // if no name or nickname - try and use displayName as W3Contact must have displayName or ContactName + [self setValue:[aContact valueForKey:kW3ContactDisplayName] forProperty:kABPersonNicknameProperty + inRecord:person asUpdate:bUpdate]; + } + + // set phoneNumbers + // NSLog(@"setting phoneNumbers"); + NSArray* array = [aContact valueForKey:kW3ContactPhoneNumbers]; + if ([array isKindOfClass:[NSArray class]]) { + [self setMultiValueStrings:array forProperty:kABPersonPhoneProperty inRecord:person asUpdate:bUpdate]; + } + // set Emails + // NSLog(@"setting emails"); + array = [aContact valueForKey:kW3ContactEmails]; + if ([array isKindOfClass:[NSArray class]]) { + [self setMultiValueStrings:array forProperty:kABPersonEmailProperty inRecord:person asUpdate:bUpdate]; + } + // set Urls + // NSLog(@"setting urls"); + array = [aContact valueForKey:kW3ContactUrls]; + if ([array isKindOfClass:[NSArray class]]) { + [self setMultiValueStrings:array forProperty:kABPersonURLProperty inRecord:person asUpdate:bUpdate]; + } + + // set multivalue dictionary properties + // set addresses: streetAddress, locality, region, postalCode, country + // set ims: value = username, type = servicetype + // iOS addresses and im are a MultiValue Properties with label, value=dictionary of info, and id + // NSLog(@"setting addresses"); + error = nil; + array = [aContact valueForKey:kW3ContactAddresses]; + if ([array isKindOfClass:[NSArray class]]) { + [self setMultiValueDictionary:array forProperty:kABPersonAddressProperty inRecord:person asUpdate:bUpdate]; + } + // ims + // NSLog(@"setting ims"); + array = [aContact valueForKey:kW3ContactIms]; + if ([array isKindOfClass:[NSArray class]]) { + [self setMultiValueDictionary:array forProperty:kABPersonInstantMessageProperty inRecord:person asUpdate:bUpdate]; + } + + // organizations + // W3C ContactOrganization has pref, type, name, title, department + // iOS only supports name, title, department + // NSLog(@"setting organizations"); + // TODO this may need work - should Organization information be removed when array is empty?? + array = [aContact valueForKey:kW3ContactOrganizations]; // iOS only supports one organization - use first one + if ([array isKindOfClass:[NSArray class]]) { + BOOL bRemove = NO; + NSDictionary* dict = nil; + if ([array count] > 0) { + dict = [array objectAtIndex:0]; + } else { + // remove the organization info entirely + 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]; + } + } + // add dates + // Dates come in as milliseconds in NSNumber Object + id ms = [aContact valueForKey:kW3ContactBirthday]; + NSDate* aDate = nil; + if (ms && [ms isKindOfClass:[NSNumber class]]) { + double msValue = [ms doubleValue]; + msValue = msValue / 1000; + aDate = [NSDate dateWithTimeIntervalSince1970:msValue]; + } + if ((aDate != nil) || [ms isKindOfClass:[NSString class]]) { + [self setValue:aDate != nil ? aDate:ms forProperty:kABPersonBirthdayProperty inRecord:person asUpdate:bUpdate]; + } + // don't update creation date + // modification date will get updated when save + // anniversary is removed from W3C Contact api Dec 9, 2010 spec - don't waste time on it yet + + // kABPersonDateProperty + + // kABPersonAnniversaryLabel + + // iOS doesn't have gender - ignore + // note + [self setValue:[aContact valueForKey:kW3ContactNote] forProperty:kABPersonNoteProperty inRecord:person asUpdate:bUpdate]; + + // iOS doesn't have preferredName- ignore + + // photo + array = [aContact valueForKey:kW3ContactPhotos]; + if ([array isKindOfClass:[NSArray class]]) { + if (bUpdate && ([array count] == 0)) { + // remove photo + bSuccess = ABPersonRemoveImageData(person, &error); + } else if ([array count] > 0) { + NSDictionary* dict = [array objectAtIndex:0]; // currently only support one photo + if ([dict isKindOfClass:[NSDictionary class]]) { + id value = [dict objectForKey:kW3ContactFieldValue]; + if ([value isKindOfClass:[NSString class]]) { + if (bUpdate && ([value length] == 0)) { + // remove the current image + bSuccess = ABPersonRemoveImageData(person, &error); + } else { + // use this image + // don't know if string is encoded or not so first unencode it then encode it again + NSString* cleanPath = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSURL* photoUrl = [NSURL URLWithString:[cleanPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + // caller is responsible for checking for a connection, if no connection this will fail + NSError* err = nil; + NSData* data = nil; + if (photoUrl) { + data = [NSData dataWithContentsOfURL:photoUrl options:NSDataReadingUncached error:&err]; + } + if (data && ([data length] > 0)) { + bSuccess = ABPersonSetImageData(person, (__bridge CFDataRef)data, &error); + } + if (!data || !bSuccess) { + NSLog(@"error setting contact image: %@", (err != nil ? [err localizedDescription] : @"")); + } + } + } + } + } + } + + // TODO WebURLs + + // TODO timezone + + return bSuccess; +} + +/* Set item into an AddressBook Record for the specified property. + * aValue - the value to set into the address book (code checks for null or [NSNull null] + * aProperty - AddressBook property ID + * aRecord - the record to update + * bUpdate - whether this is a possible update vs a new entry + * RETURN + * true - property was set (or input value as null) + * false - property was not set + */ +- (bool)setValue:(id)aValue forProperty:(ABPropertyID)aProperty inRecord:(ABRecordRef)aRecord asUpdate:(BOOL)bUpdate +{ + bool bSuccess = true; // if property was null, just ignore and return success + CFErrorRef error; + + if (aValue && ![aValue isKindOfClass:[NSNull class]]) { + if (bUpdate && ([aValue isKindOfClass:[NSString class]] && ([aValue length] == 0))) { // if updating, empty string means to delete + aValue = NULL; + } // really only need to set if different - more efficient to just update value or compare and only set if necessary??? + bSuccess = ABRecordSetValue(aRecord, aProperty, (__bridge CFTypeRef)aValue, &error); + if (!bSuccess) { + NSLog(@"error setting %d property", aProperty); + } + } + + return bSuccess; +} + +- (bool)removeProperty:(ABPropertyID)aProperty inRecord:(ABRecordRef)aRecord +{ + CFErrorRef err; + bool bSuccess = ABRecordRemoveValue(aRecord, aProperty, &err); + + if (!bSuccess) { + CFStringRef errDescription = CFErrorCopyDescription(err); + NSLog(@"Unable to remove property %d: %@", aProperty, errDescription); + CFRelease(errDescription); + } + return bSuccess; +} + +- (bool)addToMultiValue:(ABMultiValueRef)multi fromDictionary:dict +{ + bool bSuccess = FALSE; + id value = [dict valueForKey:kW3ContactFieldValue]; + + if (IS_VALID_VALUE(value)) { + CFStringRef label = [CDVContact convertContactTypeToPropertyLabel:[dict valueForKey:kW3ContactFieldType]]; + bSuccess = ABMultiValueAddValueAndLabel(multi, (__bridge CFTypeRef)value, label, NULL); + if (!bSuccess) { + NSLog(@"Error setting Value: %@ and label: %@", value, label); + } + } + return bSuccess; +} + +- (ABMultiValueRef)allocStringMultiValueFromArray:array +{ + ABMutableMultiValueRef multi = ABMultiValueCreateMutable(kABMultiStringPropertyType); + + for (NSDictionary* dict in array) { + [self addToMultiValue:multi fromDictionary:dict]; + } + + return multi; // caller is responsible for releasing multi +} + +- (bool)setValue:(CFTypeRef)value forProperty:(ABPropertyID)prop inRecord:(ABRecordRef)person +{ + CFErrorRef error; + bool bSuccess = ABRecordSetValue(person, prop, value, &error); + + if (!bSuccess) { + NSLog(@"Error setting value for property: %d", prop); + } + return bSuccess; +} + +/* Set MultiValue string properties into Address Book Record. + * NSArray* fieldArray - array of dictionaries containing W3C properties to be set into record + * ABPropertyID prop - the property to be set (generally used for phones and emails) + * ABRecordRef person - the record to set values into + * BOOL bUpdate - whether or not to update date or set as new. + * When updating: + * empty array indicates to remove entire property + * empty string indicates to remove + * [NSNull null] do not modify (keep existing record value) + * RETURNS + * bool false indicates error + * + * used for phones and emails + */ +- (bool)setMultiValueStrings:(NSArray*)fieldArray forProperty:(ABPropertyID)prop inRecord:(ABRecordRef)person asUpdate:(BOOL)bUpdate +{ + bool bSuccess = TRUE; + ABMutableMultiValueRef multi = nil; + + if (!bUpdate) { + multi = [self allocStringMultiValueFromArray:fieldArray]; + bSuccess = [self setValue:multi forProperty:prop inRecord:person]; + } else if (bUpdate && ([fieldArray count] == 0)) { + // remove entire property + bSuccess = [self removeProperty:prop inRecord:person]; + } else { // check for and apply changes + ABMultiValueRef copy = ABRecordCopyValue(person, prop); + if (copy != nil) { + multi = ABMultiValueCreateMutableCopy(copy); + CFRelease(copy); + + for (NSDictionary* dict in fieldArray) { + id val; + NSString* label = nil; + val = [dict valueForKey:kW3ContactFieldValue]; + label = (__bridge NSString*)[CDVContact convertContactTypeToPropertyLabel:[dict valueForKey:kW3ContactFieldType]]; + if (IS_VALID_VALUE(val)) { + // is an update, find index of entry with matching id, if values are different, update. + id idValue = [dict valueForKey:kW3ContactFieldId]; + int identifier = [idValue isKindOfClass:[NSNumber class]] ? [idValue intValue] : -1; + CFIndex i = identifier >= 0 ? ABMultiValueGetIndexForIdentifier(multi, identifier) : kCFNotFound; + if (i != kCFNotFound) { + if ([val length] == 0) { + // remove both value and label + ABMultiValueRemoveValueAndLabelAtIndex(multi, i); + } else { + NSString* valueAB = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(multi, i); + NSString* labelAB = (__bridge_transfer NSString*)ABMultiValueCopyLabelAtIndex(multi, i); + if ((valueAB == nil) || ![val isEqualToString:valueAB]) { + ABMultiValueReplaceValueAtIndex(multi, (__bridge CFTypeRef)val, i); + } + if ((labelAB == nil) || ![label isEqualToString:labelAB]) { + ABMultiValueReplaceLabelAtIndex(multi, (__bridge CFStringRef)label, i); + } + } + } else { + // is a new value - insert + [self addToMultiValue:multi fromDictionary:dict]; + } + } // end of if value + } // end of for + } else { // adding all new value(s) + multi = [self allocStringMultiValueFromArray:fieldArray]; + } + // set the (updated) copy as the new value + bSuccess = [self setValue:multi forProperty:prop inRecord:person]; + } + + if (multi) { + CFRelease(multi); + } + + return bSuccess; +} + +// used for ims and addresses +- (ABMultiValueRef)allocDictMultiValueFromArray:array forProperty:(ABPropertyID)prop +{ + ABMutableMultiValueRef multi = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType); + NSMutableDictionary* newDict; + NSMutableDictionary* addDict; + + for (NSDictionary* dict in array) { + newDict = [self translateW3Dict:dict forProperty:prop]; + addDict = [NSMutableDictionary dictionaryWithCapacity:2]; + if (newDict) { // create a new dictionary with a Label and Value, value is the dictionary previously created + // June, 2011 W3C Contact spec adds type into ContactAddress book + // get the type out of the original dictionary for address + NSString* addrType = (NSString*)[dict valueForKey:kW3ContactFieldType]; + if (!addrType) { + addrType = (NSString*)kABOtherLabel; + } + NSObject* typeValue = ((prop == kABPersonInstantMessageProperty) ? (NSObject*)kABOtherLabel : addrType); + // NSLog(@"typeValue: %@", typeValue); + [addDict setObject:typeValue forKey:kW3ContactFieldType]; // im labels will be set as Other and address labels as type from dictionary + [addDict setObject:newDict forKey:kW3ContactFieldValue]; + [self addToMultiValue:multi fromDictionary:addDict]; + } + } + + return multi; // caller is responsible for releasing +} + +// used for ims and addresses to convert W3 dictionary of values to AB Dictionary +// got messier when June, 2011 W3C Contact spec added type field into ContactAddress +- (NSMutableDictionary*)translateW3Dict:(NSDictionary*)dict forProperty:(ABPropertyID)prop +{ + NSArray* propArray = [[CDVContact defaultObjectAndProperties] valueForKey:[[CDVContact defaultABtoW3C] objectForKey:[NSNumber numberWithInt:prop]]]; + + NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:1]; + id value; + + for (NSString* key in propArray) { // for each W3 Contact key get the value + if (((value = [dict valueForKey:key]) != nil) && ![value isKindOfClass:[NSNull class]]) { + // if necessary convert the W3 value to AB Property label + NSString* setValue = value; + if ([CDVContact needsConversion:key]) { // IM types must be converted + setValue = (NSString*)[CDVContact convertContactTypeToPropertyLabel:value]; + // IMs must have a valid AB value! + if ((prop == kABPersonInstantMessageProperty) && [setValue isEqualToString:(NSString*)kABOtherLabel]) { + setValue = @""; // try empty string + } + } + // set the AB value into the dictionary + [newDict setObject:setValue forKey:(NSString*)[[CDVContact defaultW3CtoAB] valueForKey:(NSString*)key]]; + } + } + + if ([newDict count] == 0) { + newDict = nil; // no items added + } + return newDict; +} + +/* set multivalue dictionary properties into an AddressBook Record + * NSArray* array - array of dictionaries containing the W3C properties to set into the record + * ABPropertyID prop - the property id for the multivalue dictionary (addresses and ims) + * ABRecordRef person - the record to set the values into + * BOOL bUpdate - YES if this is an update to an existing record + * When updating: + * empty array indicates to remove entire property + * value/label == "" indicates to remove + * value/label == [NSNull null] do not modify (keep existing record value) + * RETURN + * bool false indicates fatal error + * + * iOS addresses and im are a MultiValue Properties with label, value=dictionary of info, and id + * set addresses: streetAddress, locality, region, postalCode, country + * set ims: value = username, type = servicetype + * there are some special cases in here for ims - needs cleanup / simplification + * + */ +- (bool)setMultiValueDictionary:(NSArray*)array forProperty:(ABPropertyID)prop inRecord:(ABRecordRef)person asUpdate:(BOOL)bUpdate +{ + bool bSuccess = FALSE; + ABMutableMultiValueRef multi = nil; + + if (!bUpdate) { + multi = [self allocDictMultiValueFromArray:array forProperty:prop]; + bSuccess = [self setValue:multi forProperty:prop inRecord:person]; + } else if (bUpdate && ([array count] == 0)) { + // remove property + bSuccess = [self removeProperty:prop inRecord:person]; + } else { // check for and apply changes + ABMultiValueRef copy = ABRecordCopyValue(person, prop); + if (copy) { + multi = ABMultiValueCreateMutableCopy(copy); + CFRelease(copy); + // get the W3C values for this property + NSArray* propArray = [[CDVContact defaultObjectAndProperties] valueForKey:[[CDVContact defaultABtoW3C] objectForKey:[NSNumber numberWithInt:prop]]]; + id value; + id valueAB; + + for (NSDictionary* field in array) { + NSMutableDictionary* dict; + // find the index for the current property + id idValue = [field valueForKey:kW3ContactFieldId]; + int identifier = [idValue isKindOfClass:[NSNumber class]] ? [idValue intValue] : -1; + CFIndex idx = identifier >= 0 ? ABMultiValueGetIndexForIdentifier(multi, identifier) : kCFNotFound; + BOOL bUpdateLabel = NO; + if (idx != kCFNotFound) { + dict = [NSMutableDictionary dictionaryWithCapacity:1]; + // NSDictionary* existingDictionary = (NSDictionary*)ABMultiValueCopyValueAtIndex(multi, idx); + CFTypeRef existingDictionary = ABMultiValueCopyValueAtIndex(multi, idx); + NSString* existingABLabel = (__bridge_transfer NSString*)ABMultiValueCopyLabelAtIndex(multi, idx); + NSString* testLabel = [field valueForKey:kW3ContactFieldType]; + // fixes cb-143 where setting empty label could cause address to not be removed + // (because empty label would become 'other' in convertContactTypeToPropertyLabel + // which may not have matched existing label thus resulting in an incorrect updating of the label + // and the address not getting removed at the end of the for loop) + if (testLabel && [testLabel isKindOfClass:[NSString class]] && ([testLabel length] > 0)) { + CFStringRef w3cLabel = [CDVContact convertContactTypeToPropertyLabel:testLabel]; + if (w3cLabel && ![existingABLabel isEqualToString:(__bridge NSString*)w3cLabel]) { + // replace the label + ABMultiValueReplaceLabelAtIndex(multi, w3cLabel, idx); + bUpdateLabel = YES; + } + } // else was invalid or empty label string so do not update + + for (id k in propArray) { + value = [field valueForKey:k]; + bool bSet = (value != nil && ![value isKindOfClass:[NSNull class]] && ([value isKindOfClass:[NSString class]] && [value length] > 0)); + // if there is a contact value, put it into dictionary + if (bSet) { + NSString* setValue = [CDVContact needsConversion:(NSString*)k] ? (NSString*)[CDVContact convertContactTypeToPropertyLabel:value] : value; + [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]]; + if (valueAB != nil) { + [dict setValue:valueAB forKey:[[CDVContact defaultW3CtoAB] valueForKey:k]]; + } + } // else if value == "" it will not be added into updated dict and thus removed + } // end of for loop (moving here fixes cb-143, need to end for loop before replacing or removing multivalue) + + if ([dict count] > 0) { + // something was added into new dict, + ABMultiValueReplaceValueAtIndex(multi, (__bridge CFTypeRef)dict, idx); + } else if (!bUpdateLabel) { + // nothing added into new dict and no label change so remove this property entry + ABMultiValueRemoveValueAndLabelAtIndex(multi, idx); + } + + CFRelease(existingDictionary); + } else { + // not found in multivalue so add it + dict = [self translateW3Dict:field forProperty:prop]; + if (dict) { + NSMutableDictionary* addDict = [NSMutableDictionary dictionaryWithCapacity:2]; + // get the type out of the original dictionary for address + NSObject* typeValue = ((prop == kABPersonInstantMessageProperty) ? (NSObject*)kABOtherLabel : (NSString*)[field valueForKey:kW3ContactFieldType]); + // NSLog(@"typeValue: %@", typeValue); + [addDict setObject:typeValue forKey:kW3ContactFieldType]; // im labels will be set as Other and address labels as type from dictionary + [addDict setObject:dict forKey:kW3ContactFieldValue]; + [self addToMultiValue:multi fromDictionary:addDict]; + } + } + } // end of looping through dictionaries + + // set the (updated) copy as the new value + bSuccess = [self setValue:multi forProperty:prop inRecord:person]; + } + } // end of copy and apply changes + if (multi) { + CFRelease(multi); + } + + return bSuccess; +} + +/* Determine which W3C labels need to be converted + */ ++ (BOOL)needsConversion:(NSString*)W3Label +{ + BOOL bConvert = NO; + + if ([W3Label isEqualToString:kW3ContactFieldType] || [W3Label isEqualToString:kW3ContactImType]) { + bConvert = YES; + } + return bConvert; +} + +/* Translation of property type labels contact API ---> iPhone + * + * phone: work, home, other, mobile, fax, pager --> + * kABWorkLabel, kABHomeLabel, kABOtherLabel, kABPersonPhoneMobileLabel, kABPersonHomeFAXLabel || kABPersonHomeFAXLabel, kABPersonPhonePagerLabel + * emails: work, home, other ---> kABWorkLabel, kABHomeLabel, kABOtherLabel + * ims: aim, gtalk, icq, xmpp, msn, skype, qq, yahoo --> kABPersonInstantMessageService + (AIM, ICG, MSN, Yahoo). No support for gtalk, xmpp, skype, qq + * addresses: work, home, other --> kABWorkLabel, kABHomeLabel, kABOtherLabel + * + * + */ ++ (CFStringRef)convertContactTypeToPropertyLabel:(NSString*)label +{ + CFStringRef type; + + if ([label isKindOfClass:[NSNull class]] || ![label isKindOfClass:[NSString class]]) { + type = NULL; // no label + } else if ([label caseInsensitiveCompare:kW3ContactWorkLabel] == NSOrderedSame) { + type = kABWorkLabel; + } else if ([label caseInsensitiveCompare:kW3ContactHomeLabel] == NSOrderedSame) { + type = kABHomeLabel; + } else if ([label caseInsensitiveCompare:kW3ContactOtherLabel] == NSOrderedSame) { + type = kABOtherLabel; + } else if ([label caseInsensitiveCompare:kW3ContactPhoneMobileLabel] == NSOrderedSame) { + type = kABPersonPhoneMobileLabel; + } else if ([label caseInsensitiveCompare:kW3ContactPhonePagerLabel] == NSOrderedSame) { + type = kABPersonPhonePagerLabel; + } else if ([label caseInsensitiveCompare:kW3ContactImAIMLabel] == NSOrderedSame) { + type = kABPersonInstantMessageServiceAIM; + } else if ([label caseInsensitiveCompare:kW3ContactImICQLabel] == NSOrderedSame) { + type = kABPersonInstantMessageServiceICQ; + } else if ([label caseInsensitiveCompare:kW3ContactImMSNLabel] == NSOrderedSame) { + type = kABPersonInstantMessageServiceMSN; + } else if ([label caseInsensitiveCompare:kW3ContactImYahooLabel] == NSOrderedSame) { + type = kABPersonInstantMessageServiceYahoo; + } else if ([label caseInsensitiveCompare:kW3ContactUrlProfile] == NSOrderedSame) { + type = kABPersonHomePageLabel; + } else { + type = kABOtherLabel; + } + + return type; +} + ++ (NSString*)convertPropertyLabelToContactType:(NSString*)label +{ + NSString* type = nil; + + if (label != nil) { // improve efficiency...... + if ([label isEqualToString:(NSString*)kABPersonPhoneMobileLabel]) { + type = kW3ContactPhoneMobileLabel; + } else if ([label isEqualToString:(NSString*)kABPersonPhoneHomeFAXLabel] || + [label isEqualToString:(NSString*)kABPersonPhoneWorkFAXLabel]) { + type = kW3ContactPhoneFaxLabel; + } else if ([label isEqualToString:(NSString*)kABPersonPhonePagerLabel]) { + type = kW3ContactPhonePagerLabel; + } else if ([label isEqualToString:(NSString*)kABHomeLabel]) { + type = kW3ContactHomeLabel; + } else if ([label isEqualToString:(NSString*)kABWorkLabel]) { + type = kW3ContactWorkLabel; + } else if ([label isEqualToString:(NSString*)kABOtherLabel]) { + type = kW3ContactOtherLabel; + } else if ([label isEqualToString:(NSString*)kABPersonInstantMessageServiceAIM]) { + type = kW3ContactImAIMLabel; + } else if ([label isEqualToString:(NSString*)kABPersonInstantMessageServiceICQ]) { + type = kW3ContactImICQLabel; + } else if ([label isEqualToString:(NSString*)kABPersonInstantMessageServiceJabber]) { + type = kW3ContactOtherLabel; + } else if ([label isEqualToString:(NSString*)kABPersonInstantMessageServiceMSN]) { + type = kW3ContactImMSNLabel; + } else if ([label isEqualToString:(NSString*)kABPersonInstantMessageServiceYahoo]) { + type = kW3ContactImYahooLabel; + } else if ([label isEqualToString:(NSString*)kABPersonHomePageLabel]) { + type = kW3ContactUrlProfile; + } else { + type = kW3ContactOtherLabel; + } + } + return type; +} + +/* Check if the input label is a valid W3C ContactField.type. This is used when searching, + * only search field types if the search string is a valid type. If we converted any search + * string to a ABPropertyLabel it could convert to kABOtherLabel which is probably not want + * the user wanted to search for and could skew the results. + */ ++ (BOOL)isValidW3ContactType:(NSString*)label +{ + BOOL isValid = NO; + + if ([label isKindOfClass:[NSNull class]] || ![label isKindOfClass:[NSString class]]) { + isValid = NO; // no label + } else if ([label caseInsensitiveCompare:kW3ContactWorkLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactHomeLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactOtherLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactPhoneMobileLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactPhonePagerLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactImAIMLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactImICQLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactImMSNLabel] == NSOrderedSame) { + isValid = YES; + } else if ([label caseInsensitiveCompare:kW3ContactImYahooLabel] == NSOrderedSame) { + isValid = YES; + } else { + isValid = NO; + } + + return isValid; +} + +/* Create a new Contact Dictionary object from an ABRecordRef that contains information in a format such that + * it can be returned to JavaScript callback as JSON object string. + * Uses: + * ABRecordRef set into Contact Object + * NSDictionary withFields indicates which fields to return from the AddressBook Record + * + * JavaScript Contact: + * @param {DOMString} id unique identifier + * @param {DOMString} displayName + * @param {ContactName} name + * @param {DOMString} nickname + * @param {ContactField[]} phoneNumbers array of phone numbers + * @param {ContactField[]} emails array of email addresses + * @param {ContactAddress[]} addresses array of addresses + * @param {ContactField[]} ims instant messaging user ids + * @param {ContactOrganization[]} organizations + * @param {DOMString} published date contact was first created + * @param {DOMString} updated date contact was last updated + * @param {DOMString} birthday contact's birthday + * @param (DOMString} anniversary contact's anniversary + * @param {DOMString} gender contact's gender + * @param {DOMString} note user notes about contact + * @param {DOMString} preferredUsername + * @param {ContactField[]} photos + * @param {ContactField[]} tags + * @param {ContactField[]} relationships + * @param {ContactField[]} urls contact's web sites + * @param {ContactAccounts[]} accounts contact's online accounts + * @param {DOMString} timezone UTC time zone offset + * @param {DOMString} connected + */ + +- (NSDictionary*)toDictionary:(NSDictionary*)withFields +{ + // if not a person type record bail out for now + if (ABRecordGetRecordType(self.record) != kABPersonType) { + return NULL; + } + id value = nil; + self.returnFields = withFields; + + NSMutableDictionary* nc = [NSMutableDictionary dictionaryWithCapacity:1]; // new contact dictionary to fill in from ABRecordRef + // id + [nc setObject:[NSNumber numberWithInt:ABRecordGetRecordID(self.record)] forKey:kW3ContactId]; + if (self.returnFields == nil) { + // if no returnFields specified, W3C says to return empty contact (but Cordova will at least return id) + return nc; + } + if ([self.returnFields objectForKey:kW3ContactDisplayName]) { + // displayname requested - iOS doesn't have so return null + [nc setObject:[NSNull null] forKey:kW3ContactDisplayName]; + // may overwrite below if requested ContactName and there are no values + } + // nickname + if ([self.returnFields valueForKey:kW3ContactNickname]) { + value = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, kABPersonNicknameProperty); + [nc setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactNickname]; + } + + // name dictionary + // NSLog(@"getting name info"); + NSObject* data = [self extractName]; + if (data != nil) { + [nc setObject:data forKey:kW3ContactName]; + } + 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); + if (tryName != nil) { + [nc setObject:tryName forKey:kW3ContactDisplayName]; + } else { + // use nickname or empty string + value = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, kABPersonNicknameProperty); + [nc setObject:(value != nil) ? value:@"" forKey:kW3ContactDisplayName]; + } + } + // phoneNumbers array + // NSLog(@"getting phoneNumbers"); + value = [self extractMultiValue:kW3ContactPhoneNumbers]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactPhoneNumbers]; + } + // emails array + // NSLog(@"getting emails"); + value = [self extractMultiValue:kW3ContactEmails]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactEmails]; + } + // urls array + value = [self extractMultiValue:kW3ContactUrls]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactUrls]; + } + // addresses array + // NSLog(@"getting addresses"); + value = [self extractAddresses]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactAddresses]; + } + // im array + // NSLog(@"getting ims"); + value = [self extractIms]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactIms]; + } + // organization array (only info for one organization in iOS) + // NSLog(@"getting organizations"); + value = [self extractOrganizations]; + if (value != nil) { + [nc setObject:value forKey:kW3ContactOrganizations]; + } + + // for simple properties, could make this a bit more efficient by storing all simple properties in a single + // array in the returnFields dictionary and setting them via a for loop through the array + + // add dates + // NSLog(@"getting dates"); + NSNumber* ms; + + /** Contact Revision field removed from June 16, 2011 version of specification + + if ([self.returnFields valueForKey:kW3ContactUpdated]){ + ms = [self getDateAsNumber: kABPersonModificationDateProperty]; + if (!ms){ + // try and get published date + ms = [self getDateAsNumber: kABPersonCreationDateProperty]; + } + if (ms){ + [nc setObject: ms forKey:kW3ContactUpdated]; + } + + } + */ + + if ([self.returnFields valueForKey:kW3ContactBirthday]) { + ms = [self getDateAsNumber:kABPersonBirthdayProperty]; + if (ms) { + [nc setObject:ms forKey:kW3ContactBirthday]; + } + } + + /* Anniversary removed from 12-09-2010 W3C Contacts api spec + if ([self.returnFields valueForKey:kW3ContactAnniversary]){ + // Anniversary date is stored in a multivalue property + ABMultiValueRef multi = ABRecordCopyValue(self.record, kABPersonDateProperty); + if (multi){ + CFStringRef label = nil; + CFIndex count = ABMultiValueGetCount(multi); + // see if contains an Anniversary date + for(CFIndex i=0; i<count; i++){ + label = ABMultiValueCopyLabelAtIndex(multi, i); + if(label && [(NSString*)label isEqualToString:(NSString*)kABPersonAnniversaryLabel]){ + CFDateRef aDate = ABMultiValueCopyValueAtIndex(multi, i); + if(aDate){ + [nc setObject: (NSString*)aDate forKey: kW3ContactAnniversary]; + CFRelease(aDate); + } + CFRelease(label); + break; + } + } + CFRelease(multi); + } + }*/ + + if ([self.returnFields valueForKey:kW3ContactNote]) { + // note + value = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, kABPersonNoteProperty); + [nc setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactNote]; + } + + if ([self.returnFields valueForKey:kW3ContactPhotos]) { + value = [self extractPhotos]; + [nc setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactPhotos]; + } + + /* TimeZone removed from June 16, 2011 Contacts spec + * + if ([self.returnFields valueForKey:kW3ContactTimezone]){ + [NSTimeZone resetSystemTimeZone]; + NSTimeZone* currentTZ = [NSTimeZone localTimeZone]; + NSInteger seconds = [currentTZ secondsFromGMT]; + NSString* tz = [NSString stringWithFormat:@"%2d:%02u", seconds/3600, seconds % 3600 ]; + [nc setObject:tz forKey:kW3ContactTimezone]; + } + */ + // TODO WebURLs + // [nc setObject:[NSNull null] forKey:kW3ContactUrls]; + // online accounts - not available on iOS + + return nc; +} + +- (NSNumber*)getDateAsNumber:(ABPropertyID)datePropId +{ + NSNumber* msDate = nil; + NSDate* aDate = nil; + CFTypeRef cfDate = ABRecordCopyValue(self.record, datePropId); + + if (cfDate) { + aDate = (__bridge NSDate*)cfDate; + msDate = [NSNumber numberWithDouble:([aDate timeIntervalSince1970] * 1000)]; + CFRelease(cfDate); + } + return msDate; +} + +/* Create Dictionary to match JavaScript ContactName object: + * formatted - ABRecordCopyCompositeName + * familyName + * givenName + * middleName + * honorificPrefix + * honorificSuffix +*/ + +- (NSObject*)extractName +{ + NSArray* fields = [self.returnFields objectForKey:kW3ContactName]; + + if (fields == nil) { // no name fields requested + return nil; + } + + NSMutableDictionary* newName = [NSMutableDictionary dictionaryWithCapacity:6]; + id value; + + for (NSString* i in fields) { + if ([i isEqualToString:kW3ContactFormattedName]) { + value = (__bridge_transfer NSString*)ABRecordCopyCompositeName(self.record); + [newName setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFormattedName]; + } else { + // W3CtoAB returns NSNumber for AB name properties, get intValue and cast to ABPropertyID) + value = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, (ABPropertyID)[[[CDVContact defaultW3CtoAB] valueForKey:i] intValue]); + [newName setObject:(value != nil) ? value:[NSNull null] forKey:(NSString*)i]; + } + } + + return newName; +} + +/* Create array of Dictionaries to match JavaScript ContactField object for simple multiValue properties phoneNumbers, emails + * Input: (NSString*) W3Contact Property name + * type + * for phoneNumbers type is one of (work,home,other, mobile, fax, pager) + * for emails type is one of (work,home, other) + * value - phone number or email address + * (bool) primary (not supported on iphone) + * id +*/ +- (NSObject*)extractMultiValue:(NSString*)propertyId +{ + NSArray* fields = [self.returnFields objectForKey:propertyId]; + + if (fields == nil) { + return nil; + } + ABMultiValueRef multi = nil; + NSObject* valuesArray = nil; + NSNumber* propNumber = [[CDVContact defaultW3CtoAB] valueForKey:propertyId]; + ABPropertyID propId = [propNumber intValue]; + multi = ABRecordCopyValue(self.record, propId); + // multi = ABRecordCopyValue(self.record, (ABPropertyID)[[[Contact defaultW3CtoAB] valueForKey:propertyId] intValue]); + CFIndex count = multi != nil ? ABMultiValueGetCount(multi) : 0; + id value; + if (count) { + valuesArray = [NSMutableArray arrayWithCapacity:count]; + + for (CFIndex i = 0; i < count; i++) { + NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:4]; + if ([fields containsObject:kW3ContactFieldType]) { + NSString* label = (__bridge_transfer NSString*)ABMultiValueCopyLabelAtIndex(multi, i); + value = [CDVContact convertPropertyLabelToContactType:label]; + [newDict setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFieldType]; + } + if ([fields containsObject:kW3ContactFieldValue]) { + value = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(multi, i); + [newDict setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFieldValue]; + } + if ([fields containsObject:kW3ContactFieldPrimary]) { + [newDict setObject:[NSNumber numberWithBool:(BOOL)NO] forKey:kW3ContactFieldPrimary]; // iOS doesn't support primary so set all to false + } + // always set id + value = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; + [newDict setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFieldId]; + [(NSMutableArray*) valuesArray addObject:newDict]; + } + } else { + valuesArray = [NSNull null]; + } + if (multi) { + CFRelease(multi); + } + + return valuesArray; +} + +/* Create array of Dictionaries to match JavaScript ContactAddress object for addresses + * pref - not supported + * type - address type + * formatted - formatted for mailing label (what about localization?) + * streetAddress + * locality + * region; + * postalCode + * country + * id + * + * iOS addresses are a MultiValue Properties with label, value=dictionary of address info, and id + */ +- (NSObject*)extractAddresses +{ + NSArray* fields = [self.returnFields objectForKey:kW3ContactAddresses]; + + if (fields == nil) { // no name fields requested + return nil; + } + id __weak value; + NSObject* addresses; + ABMultiValueRef multi = ABRecordCopyValue(self.record, kABPersonAddressProperty); + CFIndex count = multi ? ABMultiValueGetCount(multi) : 0; + if (count) { + addresses = [NSMutableArray arrayWithCapacity:count]; + + for (CFIndex i = 0; i < count; i++) { + NSMutableDictionary* newAddress = [NSMutableDictionary dictionaryWithCapacity:7]; + // if we got this far, at least some address info is being requested. + + // Always set id + id identifier = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; + [newAddress setObject:(identifier != nil) ? identifier:[NSNull null] forKey:kW3ContactFieldId]; + // set the type label + NSString* label = (__bridge_transfer NSString*)ABMultiValueCopyLabelAtIndex(multi, i); + + [newAddress setObject:(label != nil) ? (NSObject*)[[CDVContact class] convertPropertyLabelToContactType:label]:[NSNull null] forKey:kW3ContactFieldType]; + // set the pref - iOS doesn't support so set to default of false + [newAddress setObject:@"false" forKey:kW3ContactFieldPrimary]; + // get dictionary of values for this address + CFDictionaryRef dict = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(multi, i); + + for (id k in fields) { + bool bFound; + id key = [[CDVContact defaultW3CtoAB] valueForKey:k]; + if (key && ![k isKindOfClass:[NSNull class]]) { + bFound = CFDictionaryGetValueIfPresent(dict, (__bridge const void*)key, (void*)&value); + [newAddress setObject:(bFound && value != NULL) ? (id) value:[NSNull null] forKey:k]; + } else { + // was a property that iPhone doesn't support + [newAddress setObject:[NSNull null] forKey:k]; + } + } + + if ([newAddress count] > 0) { // ?? this will always be true since we set id,label,primary field?? + [(NSMutableArray*) addresses addObject:newAddress]; + } + CFRelease(dict); + } // end of loop through addresses + } else { + addresses = [NSNull null]; + } + if (multi) { + CFRelease(multi); + } + + return addresses; +} + +/* Create array of Dictionaries to match JavaScript ContactField object for ims + * type one of [aim, gtalk, icq, xmpp, msn, skype, qq, yahoo] needs other as well + * value + * (bool) primary + * id + * + * iOS IMs are a MultiValue Properties with label, value=dictionary of IM details (service, username), and id + */ +- (NSObject*)extractIms +{ + NSArray* fields = [self.returnFields objectForKey:kW3ContactIms]; + + if (fields == nil) { // no name fields requested + return nil; + } + NSObject* imArray; + ABMultiValueRef multi = ABRecordCopyValue(self.record, kABPersonInstantMessageProperty); + CFIndex count = multi ? ABMultiValueGetCount(multi) : 0; + if (count) { + imArray = [NSMutableArray arrayWithCapacity:count]; + + for (CFIndex i = 0; i < ABMultiValueGetCount(multi); i++) { + NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:3]; + // iOS has label property (work, home, other) for each IM but W3C contact API doesn't use + CFDictionaryRef dict = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(multi, i); + NSString* __weak value; // all values should be CFStringRefs / NSString* + bool bFound; + if ([fields containsObject:kW3ContactFieldValue]) { + // value = user name + bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageUsernameKey, (void*)&value); + [newDict setObject:(bFound && value != NULL) ? (id) value:[NSNull null] forKey:kW3ContactFieldValue]; + } + if ([fields containsObject:kW3ContactFieldType]) { + bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageServiceKey, (void*)&value); + [newDict setObject:(bFound && value != NULL) ? (id)[[CDVContact class] convertPropertyLabelToContactType:value]:[NSNull null] forKey:kW3ContactFieldType]; + } + // always set ID + id identifier = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; + [newDict setObject:(identifier != nil) ? identifier:[NSNull null] forKey:kW3ContactFieldId]; + + [(NSMutableArray*) imArray addObject:newDict]; + CFRelease(dict); + } + } else { + imArray = [NSNull null]; + } + + if (multi) { + CFRelease(multi); + } + return imArray; +} + +/* Create array of Dictionaries to match JavaScript ContactOrganization object + * pref - not supported in iOS + * type - not supported in iOS + * name + * department + * title + */ + +- (NSObject*)extractOrganizations +{ + NSArray* fields = [self.returnFields objectForKey:kW3ContactOrganizations]; + + if (fields == nil) { // no name fields requested + return nil; + } + NSObject* array = nil; + NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:5]; + id value; + int validValueCount = 0; + + for (id i in fields) { + id key = [[CDVContact defaultW3CtoAB] valueForKey:i]; + if (key && [key isKindOfClass:[NSNumber class]]) { + value = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, (ABPropertyID)[[[CDVContact defaultW3CtoAB] valueForKey:i] intValue]); + if (value != nil) { + // if there are no organization values we should return null for organization + // this counter keeps indicates if any organization values have been set + validValueCount++; + } + [newDict setObject:(value != nil) ? value:[NSNull null] forKey:i]; + } else { // not a key iOS supports, set to null + [newDict setObject:[NSNull null] forKey:i]; + } + } + + if (([newDict count] > 0) && (validValueCount > 0)) { + // add pref and type + // they are not supported by iOS and thus these values never change + [newDict setObject:@"false" forKey:kW3ContactFieldPrimary]; + [newDict setObject:[NSNull null] forKey:kW3ContactFieldType]; + array = [NSMutableArray arrayWithCapacity:1]; + [(NSMutableArray*) array addObject:newDict]; + } else { + array = [NSNull null]; + } + return array; +} + +// W3C Contacts expects an array of photos. Can return photos in more than one format, currently +// just returning the default format +// Save the photo data into tmp directory and return FileURI - temp directory is deleted upon application exit +- (NSObject*)extractPhotos +{ + NSMutableArray* photos = nil; + + if (ABPersonHasImageData(self.record)) { + CFDataRef photoData = ABPersonCopyImageData(self.record); + NSData* data = (__bridge NSData*)photoData; + // write to temp directory and store URI in photos array + // get the temp directory path + NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSError* err = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + // generate unique file name + NSString* filePath; + int i = 1; + do { + filePath = [NSString stringWithFormat:@"%@/photo_%03d.jpg", docsPath, i++]; + } while ([fileMgr fileExistsAtPath:filePath]); + // save file + if ([data writeToFile:filePath options:NSAtomicWrite error:&err]) { + photos = [NSMutableArray arrayWithCapacity:1]; + NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:2]; + [newDict setObject:filePath forKey:kW3ContactFieldValue]; + [newDict setObject:@"url" forKey:kW3ContactFieldType]; + [newDict setObject:@"false" forKey:kW3ContactFieldPrimary]; + [photos addObject:newDict]; + } + + CFRelease(photoData); + } + return photos; +} + +/** + * given an array of W3C Contact field names, create a dictionary of field names to extract + * if field name represents an object, return all properties for that object: "name" - returns all properties in ContactName + * if field name is an explicit property, return only those properties: "name.givenName - returns a ContactName with only ContactName.givenName + * if field contains ONLY ["*"] return all fields + * dictionary format: + * key is W3Contact #define + * value is NSMutableArray* for complex keys: name,addresses,organizations, phone, emails, ims + * value is [NSNull null] for simple keys +*/ ++ (NSDictionary*)calcReturnFields:(NSArray*)fieldsArray // NSLog(@"getting self.returnFields"); +{ + NSMutableDictionary* d = [NSMutableDictionary dictionaryWithCapacity:1]; + + if ((fieldsArray != nil) && [fieldsArray isKindOfClass:[NSArray class]]) { + if (([fieldsArray count] == 1) && [[fieldsArray objectAtIndex:0] isEqualToString:@"*"]) { + return [CDVContact defaultFields]; // return all fields + } + + for (id i in fieldsArray) { + NSMutableArray* keys = nil; + NSString* fieldStr = nil; + if ([i isKindOfClass:[NSNumber class]]) { + fieldStr = [i stringValue]; + } else { + fieldStr = i; + } + + // see if this is specific property request in object - object.property + NSArray* parts = [fieldStr componentsSeparatedByString:@"."]; // returns original string if no separator found + NSString* name = [parts objectAtIndex:0]; + NSString* property = nil; + if ([parts count] > 1) { + property = [parts objectAtIndex:1]; + } + // see if this is a complex field by looking for its array of properties in objectAndProperties dictionary + id fields = [[CDVContact defaultObjectAndProperties] objectForKey:name]; + + // if find complex name (name,addresses,organizations, phone, emails, ims) in fields, add name as key + // with array of associated properties as the value + if ((fields != nil) && (property == nil)) { // request was for full object + keys = [NSMutableArray arrayWithArray:fields]; + if (keys != nil) { + [d setObject:keys forKey:name]; // will replace if prop array already exists + } + } else if ((fields != nil) && (property != nil)) { + // found an individual property request in form of name.property + // verify is real property name by using it as key in W3CtoAB + id abEquiv = [[CDVContact defaultW3CtoAB] objectForKey:property]; + if (abEquiv || [[CDVContact defaultW3CtoNull] containsObject:property]) { + // if existing array add to it + if ((keys = [d objectForKey:name]) != nil) { + [keys addObject:property]; + } else { + keys = [NSMutableArray arrayWithObject:property]; + [d setObject:keys forKey:name]; + } + } else { + NSLog(@"Contacts.find -- request for invalid property ignored: %@.%@", name, property); + } + } else { // is an individual property, verify is real property name by using it as key in W3CtoAB + id valid = [[CDVContact defaultW3CtoAB] objectForKey:name]; + if (valid || [[CDVContact defaultW3CtoNull] containsObject:name]) { + [d setObject:[NSNull null] forKey:name]; + } + } + } + } + if ([d count] == 0) { + // no array or nothing in the array. W3C spec says to return nothing + return nil; // [Contact defaultFields]; + } + return d; +} + +/* + * Search for the specified value in each of the fields specified in the searchFields dictionary. + * NSString* value - the string value to search for (need clarification from W3C on how to search for dates) + * NSDictionary* searchFields - a dictionary created via calcReturnFields where the key is the top level W3C + * object and the object is the array of specific fields within that object or null if it is a single property + * RETURNS + * YES as soon as a match is found in any of the fields + * NO - the specified value does not exist in any of the fields in this contact + * + * Note: I'm not a fan of returning in the middle of methods but have done it some in this method in order to + * keep the code simpler. bgibson + */ +- (BOOL)foundValue:(NSString*)testValue inFields:(NSDictionary*)searchFields +{ + BOOL bFound = NO; + + if ((testValue == nil) || ![testValue isKindOfClass:[NSString class]] || ([testValue length] == 0)) { + // nothing to find so return NO + return NO; + } + NSInteger valueAsInt = [testValue integerValue]; + + // per W3C spec, always include id in search + int recordId = ABRecordGetRecordID(self.record); + if (valueAsInt && (recordId == valueAsInt)) { + return YES; + } + + if (searchFields == nil) { + // no fields to search + return NO; + } + + if ([searchFields valueForKey:kW3ContactNickname]) { + bFound = [self testStringValue:testValue forW3CProperty:kW3ContactNickname]; + if (bFound == YES) { + return bFound; + } + } + + if ([searchFields valueForKeyIsArray:kW3ContactName]) { + // test name fields. All are string properties obtained via ABRecordCopyValue except kW3ContactFormattedName + NSArray* fields = [searchFields valueForKey:kW3ContactName]; + + for (NSString* testItem in fields) { + if ([testItem isEqualToString:kW3ContactFormattedName]) { + NSString* propValue = (__bridge_transfer NSString*)ABRecordCopyCompositeName(self.record); + if ((propValue != nil) && ([propValue length] > 0)) { + NSRange range = [propValue rangeOfString:testValue options:NSCaseInsensitiveSearch]; + bFound = (range.location != NSNotFound); + propValue = nil; + } + } else { + bFound = [self testStringValue:testValue forW3CProperty:testItem]; + } + + if (bFound) { + break; + } + } + } + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactPhoneNumbers]) { + bFound = [self searchContactFields:(NSArray*)[searchFields valueForKey:kW3ContactPhoneNumbers] + forMVStringProperty:kABPersonPhoneProperty withValue:testValue]; + } + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactEmails]) { + bFound = [self searchContactFields:(NSArray*)[searchFields valueForKey:kW3ContactEmails] + forMVStringProperty:kABPersonEmailProperty withValue:testValue]; + } + + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactAddresses]) { + bFound = [self searchContactFields:[searchFields valueForKey:kW3ContactAddresses] + forMVDictionaryProperty:kABPersonAddressProperty withValue:testValue]; + } + + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactIms]) { + bFound = [self searchContactFields:[searchFields valueForKey:kW3ContactIms] + forMVDictionaryProperty:kABPersonInstantMessageProperty withValue:testValue]; + } + + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactOrganizations]) { + NSArray* fields = [searchFields valueForKey:kW3ContactOrganizations]; + + for (NSString* testItem in fields) { + bFound = [self testStringValue:testValue forW3CProperty:testItem]; + if (bFound == YES) { + break; + } + } + } + if (!bFound && [searchFields valueForKey:kW3ContactNote]) { + bFound = [self testStringValue:testValue forW3CProperty:kW3ContactNote]; + } + + // if searching for a date field is requested, get the date field as a localized string then look for match against testValue in date string + // searching for photos is not supported + if (!bFound && [searchFields valueForKey:kW3ContactBirthday]) { + bFound = [self testDateValue:testValue forW3CProperty:kW3ContactBirthday]; + } + if (!bFound && [searchFields valueForKeyIsArray:kW3ContactUrls]) { + bFound = [self searchContactFields:(NSArray*)[searchFields valueForKey:kW3ContactUrls] + forMVStringProperty:kABPersonURLProperty withValue:testValue]; + } + + return bFound; +} + +/* + * Test for the existence of a given string within the value of a ABPersonRecord string property based on the W3c property name. + * + * IN: + * NSString* testValue - the value to find - search is case insensitive + * NSString* property - the W3c property string + * OUT: + * BOOL YES if the given string was found within the property value + * NO if the testValue was not found, W3C property string was invalid or the AddressBook property was not a string + */ +- (BOOL)testStringValue:(NSString*)testValue forW3CProperty:(NSString*)property +{ + BOOL bFound = NO; + + if ([[CDVContact defaultW3CtoAB] valueForKeyIsNumber:property]) { + ABPropertyID propId = [[[CDVContact defaultW3CtoAB] objectForKey:property] intValue]; + if (ABPersonGetTypeOfProperty(propId) == kABStringPropertyType) { + NSString* propValue = (__bridge_transfer NSString*)ABRecordCopyValue(self.record, propId); + if ((propValue != nil) && ([propValue length] > 0)) { + NSPredicate* containPred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", testValue]; + bFound = [containPred evaluateWithObject:propValue]; + // NSRange range = [propValue rangeOfString:testValue options: NSCaseInsensitiveSearch]; + // bFound = (range.location != NSNotFound); + } + } + } + return bFound; +} + +/* + * Test for the existence of a given Date string within the value of a ABPersonRecord datetime property based on the W3c property name. + * + * IN: + * NSString* testValue - the value to find - search is case insensitive + * NSString* property - the W3c property string + * OUT: + * BOOL YES if the given string was found within the localized date string value + * NO if the testValue was not found, W3C property string was invalid or the AddressBook property was not a DateTime + */ +- (BOOL)testDateValue:(NSString*)testValue forW3CProperty:(NSString*)property +{ + BOOL bFound = NO; + + if ([[CDVContact defaultW3CtoAB] valueForKeyIsNumber:property]) { + ABPropertyID propId = [[[CDVContact defaultW3CtoAB] objectForKey:property] intValue]; + if (ABPersonGetTypeOfProperty(propId) == kABDateTimePropertyType) { + NSDate* date = (__bridge_transfer NSDate*)ABRecordCopyValue(self.record, propId); + if (date != nil) { + NSString* dateString = [date descriptionWithLocale:[NSLocale currentLocale]]; + NSPredicate* containPred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", testValue]; + bFound = [containPred evaluateWithObject:dateString]; + } + } + } + return bFound; +} + +/* + * Search the specified fields within an AddressBook multivalue string property for the specified test value. + * Used for phoneNumbers, emails and urls. + * IN: + * NSArray* fields - the fields to search for within the multistring property (value and/or type) + * ABPropertyID - the property to search + * NSString* testValue - the value to search for. Will convert between W3C types and AB types. Will only + * search for types if the testValue is a valid ContactField type. + * OUT: + * YES if the test value was found in one of the specified fields + * NO if the test value was not found + */ +- (BOOL)searchContactFields:(NSArray*)fields forMVStringProperty:(ABPropertyID)propId withValue:testValue +{ + BOOL bFound = NO; + + for (NSString* type in fields) { + NSString* testString = nil; + if ([type isEqualToString:kW3ContactFieldType]) { + if ([CDVContact isValidW3ContactType:testValue]) { + // only search types if the filter string is a valid ContactField.type + testString = (NSString*)[CDVContact convertContactTypeToPropertyLabel:testValue]; + } + } else { + testString = testValue; + } + + if (testString != nil) { + bFound = [self testMultiValueStrings:testString forProperty:propId ofType:type]; + } + if (bFound == YES) { + break; + } + } + + return bFound; +} + +/* + * Searches a multiString value of the specified type for the specified test value. + * + * IN: + * NSString* testValue - the value to test for + * ABPropertyID propId - the property id of the multivalue property to search + * NSString* type - the W3C contact type to search for (value or type) + * OUT: + * YES is the test value was found + * NO if the test value was not found + */ +- (BOOL)testMultiValueStrings:(NSString*)testValue forProperty:(ABPropertyID)propId ofType:(NSString*)type +{ + BOOL bFound = NO; + + if (ABPersonGetTypeOfProperty(propId) == kABMultiStringPropertyType) { + NSArray* valueArray = nil; + if ([type isEqualToString:kW3ContactFieldType]) { + valueArray = [self labelsForProperty:propId inRecord:self.record]; + } else if ([type isEqualToString:kW3ContactFieldValue]) { + valueArray = [self valuesForProperty:propId inRecord:self.record]; + } + if (valueArray) { + NSString* valuesAsString = [valueArray componentsJoinedByString:@" "]; + NSPredicate* containPred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", testValue]; + bFound = [containPred evaluateWithObject:valuesAsString]; + } + } + return bFound; +} + +/* + * Returns the array of values for a multivalue string property of the specified property id + */ +- (__autoreleasing NSArray*)valuesForProperty:(ABPropertyID)propId inRecord:(ABRecordRef)aRecord +{ + ABMultiValueRef multi = ABRecordCopyValue(aRecord, propId); + NSArray* values = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(multi); + + CFRelease(multi); + return values; +} + +/* + * Returns the array of labels for a multivalue string property of the specified property id + */ +- (NSArray*)labelsForProperty:(ABPropertyID)propId inRecord:(ABRecordRef)aRecord +{ + ABMultiValueRef multi = ABRecordCopyValue(aRecord, propId); + CFIndex count = ABMultiValueGetCount(multi); + NSMutableArray* labels = [NSMutableArray arrayWithCapacity:count]; + + for (int i = 0; i < count; i++) { + NSString* label = (__bridge_transfer NSString*)ABMultiValueCopyLabelAtIndex(multi, i); + if (label) { + [labels addObject:label]; + } + } + + CFRelease(multi); + return labels; +} + +/* search for values within MultiValue Dictionary properties Address or IM property + * IN: + * (NSArray*) fields - the array of W3C field names to search within + * (ABPropertyID) propId - the AddressBook property that returns a multivalue dictionary + * (NSString*) testValue - the string to search for within the specified fields + * + */ +- (BOOL)searchContactFields:(NSArray*)fields forMVDictionaryProperty:(ABPropertyID)propId withValue:(NSString*)testValue +{ + BOOL bFound = NO; + + NSArray* values = [self valuesForProperty:propId inRecord:self.record]; // array of dictionaries (as CFDictionaryRef) + int dictCount = [values count]; + + // for ims dictionary contains with service (w3C type) and username (W3c value) + // for addresses dictionary contains street, city, state, zip, country + for (int i = 0; i < dictCount; i++) { + CFDictionaryRef dict = (__bridge CFDictionaryRef)[values objectAtIndex:i]; + + for (NSString* member in fields) { + NSString* abKey = [[CDVContact defaultW3CtoAB] valueForKey:member]; // im and address fields are all strings + NSString* __weak abValue = nil; + if (abKey) { + NSString* testString = nil; + if ([member isEqualToString:kW3ContactImType]) { + if ([CDVContact isValidW3ContactType:testValue]) { + // only search service/types if the filter string is a valid ContactField.type + testString = (NSString*)[CDVContact convertContactTypeToPropertyLabel:testValue]; + } + } else { + testString = testValue; + } + if (testString != nil) { + BOOL bExists = CFDictionaryGetValueIfPresent(dict, (__bridge const void*)abKey, (void*)&abValue); + if (bExists) { + NSPredicate* containPred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", testString]; + bFound = [containPred evaluateWithObject:abValue]; + } + } + } + if (bFound == YES) { + break; + } + } // end of for each member in fields + + if (bFound == YES) { + break; + } + } // end of for each dictionary + + return bFound; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVContacts.h b/iPhone/CordovaLib/Classes/CDVContacts.h new file mode 100755 index 0000000..17470c0 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVContacts.h @@ -0,0 +1,151 @@ +/* + 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> +#import <AddressBook/ABAddressBook.h> +#import <AddressBookUI/AddressBookUI.h> +#import "CDVPlugin.h" +#import "CDVContact.h" + +@interface CDVContacts : CDVPlugin <ABNewPersonViewControllerDelegate, + ABPersonViewControllerDelegate, + ABPeoplePickerNavigationControllerDelegate + > +{ + ABAddressBookRef addressBook; +} + +/* + * newContact - create a new contact via the GUI + * + * arguments: + * 1: successCallback: this is the javascript function that will be called with the newly created contactId + */ +- (void)newContact:(CDVInvokedUrlCommand*)command; + +/* + * displayContact - IN PROGRESS + * + * arguments: + * 1: recordID of the contact to display in the iPhone contact display + * 2: successCallback - currently not used + * 3: error callback + * options: + * allowsEditing: set to true to allow the user to edit the contact - currently not supported + */ +- (void)displayContact:(CDVInvokedUrlCommand*)command; + +/* + * chooseContact + * + * arguments: + * 1: this is the javascript function that will be called with the contact data as a JSON object (as the first param) + * options: + * allowsEditing: set to true to not choose the contact, but to edit it in the iPhone contact editor + */ +- (void)chooseContact:(CDVInvokedUrlCommand*)command; + +- (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person; +- (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person + property :(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue; + +/* + * search - searches for contacts. Only person records are currently supported. + * + * arguments: + * 1: successcallback - this is the javascript function that will be called with the array of found contacts + * 2: errorCallback - optional javascript function to be called in the event of an error with an error code. + * options: dictionary containing ContactFields and ContactFindOptions + * fields - ContactFields array + * findOptions - ContactFindOptions object as dictionary + * + */ +- (void)search:(CDVInvokedUrlCommand*)command; + +/* + * save - saves a new contact or updates and existing contact + * + * arguments: + * 1: success callback - this is the javascript function that will be called with the JSON representation of the saved contact + * search calls a fixed navigator.service.contacts._findCallback which then calls the success callback stored before making the call into obj-c + */ +- (void)save:(CDVInvokedUrlCommand*)command; + +/* + * remove - removes a contact from the address book + * + * arguments: + * 1: 1: successcallback - this is the javascript function that will be called with a (now) empty contact object + * + * options: dictionary containing Contact object to remove + * contact - Contact object as dictionary + */ +- (void)remove:(CDVInvokedUrlCommand*)command; + +// - (void) dealloc; + +@end + +@interface CDVContactsPicker : ABPeoplePickerNavigationController +{ + BOOL allowsEditing; + NSString* callbackId; + NSDictionary* options; + NSDictionary* pickedContactDictionary; +} + +@property BOOL allowsEditing; +@property (copy) NSString* callbackId; +@property (nonatomic, strong) NSDictionary* options; +@property (nonatomic, strong) NSDictionary* pickedContactDictionary; + +@end + +@interface CDVNewContactsController : ABNewPersonViewController +{ + NSString* callbackId; +} +@property (copy) NSString* callbackId; +@end + +/* ABPersonViewController does not have any UI to dismiss. Adding navigationItems to it does not work properly, the navigationItems are lost when the app goes into the background. + The solution was to create an empty NavController in front of the ABPersonViewController. This + causes the ABPersonViewController to have a back button. By subclassing the ABPersonViewController, + we can override viewWillDisappear and take down the entire NavigationController at that time. + */ +@interface CDVDisplayContactViewController : ABPersonViewController +{} +@property (nonatomic, strong) CDVPlugin* contactsPlugin; + +@end +@interface CDVAddressBookAccessError : NSObject +{} +@property (assign) CDVContactError errorCode; +- (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code; +@end + +typedef void (^CDVAddressBookWorkerBlock)( + ABAddressBookRef addressBook, + CDVAddressBookAccessError * error + ); +@interface CDVAddressBookHelper : NSObject +{} + +- (void)createAddressBook:(CDVAddressBookWorkerBlock)workerBlock; +@end diff --git a/iPhone/CordovaLib/Classes/CDVContacts.m b/iPhone/CordovaLib/Classes/CDVContacts.m new file mode 100755 index 0000000..3faf6ba --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVContacts.m @@ -0,0 +1,593 @@ +/* + 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 "CDVContacts.h" +#import <UIKit/UIKit.h> +#import "NSArray+Comparisons.h" +#import "NSDictionary+Extensions.h" +#import "CDVNotification.h" + +@implementation CDVContactsPicker + +@synthesize allowsEditing; +@synthesize callbackId; +@synthesize options; +@synthesize pickedContactDictionary; + +@end +@implementation CDVNewContactsController + +@synthesize callbackId; + +@end + +@implementation CDVContacts + +// no longer used since code gets AddressBook for each operation. +// If address book changes during save or remove operation, may get error but not much we can do about it +// If address book changes during UI creation, display or edit, we don't control any saves so no need for callback + +/*void addressBookChanged(ABAddressBookRef addressBook, CFDictionaryRef info, void* context) +{ + // note that this function is only called when another AddressBook instance modifies + // the address book, not the current one. For example, through an OTA MobileMe sync + Contacts* contacts = (Contacts*)context; + [contacts addressBookDirty]; +}*/ + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVContacts*)[super initWithWebView:(UIWebView*)theWebView]; + + /*if (self) { + addressBook = ABAddressBookCreate(); + ABAddressBookRegisterExternalChangeCallback(addressBook, addressBookChanged, self); + }*/ + + return self; +} + +// overridden to clean up Contact statics +- (void)onAppTerminate +{ + // NSLog(@"Contacts::onAppTerminate"); +} + +// iPhone only method to create a new contact through the GUI +- (void)newContact:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + + 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); + + npController.newPersonViewDelegate = self; + npController.callbackId = callbackId; + + 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]; + } + }]; +} + +- (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person +{ + ABRecordID recordId = kABRecordInvalidID; + CDVNewContactsController* newCP = (CDVNewContactsController*)newPersonViewController; + NSString* callbackId = newCP.callbackId; + + if (person != NULL) { + // return the contact id + recordId = ABRecordGetRecordID(person); + } + + if ([newPersonViewController respondsToSelector:@selector(presentingViewController)]) { + [[newPersonViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil]; + } else { + [[newPersonViewController parentViewController] dismissModalViewControllerAnimated:YES]; + } + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:recordId]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)displayContact:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + ABRecordID recordID = [[command.arguments objectAtIndex:0] intValue]; + NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:[NSNull null]]; + bool bEdit = [options isKindOfClass:[NSNull class]] ? false : [options existsValue:@"true" forKey:@"allowsEditing"]; + + 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; + + // create this so DisplayContactViewController will have a "back" button. + UIViewController* parentController = [[UIViewController alloc] init]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; + + [navController pushViewController:personController animated:YES]; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:navController 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]; + } + } 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 + property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue +{ + return YES; +} + +- (void)chooseContact:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:[NSNull null]]; + + CDVContactsPicker* pickerController = [[CDVContactsPicker alloc] init]; + + pickerController.peoplePickerDelegate = self; + pickerController.callbackId = callbackId; + pickerController.options = options; + pickerController.pickedContactDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kABRecordInvalidID], kW3ContactId, nil]; + pickerController.allowsEditing = (BOOL)[options existsValue : @"true" forKey : @"allowsEditing"]; + + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:pickerController animated:YES completion:nil]; + } else { + [self.viewController presentModalViewController:pickerController animated:YES]; + } +} + +- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker + shouldContinueAfterSelectingPerson:(ABRecordRef)person +{ + CDVContactsPicker* picker = (CDVContactsPicker*)peoplePicker; + NSNumber* pickedId = [NSNumber numberWithInt:ABRecordGetRecordID(person)]; + + if (picker.allowsEditing) { + ABPersonViewController* personController = [[ABPersonViewController alloc] init]; + personController.displayedPerson = person; + personController.personViewDelegate = self; + personController.allowsEditing = picker.allowsEditing; + // store id so can get info in peoplePickerNavigationControllerDidCancel + picker.pickedContactDictionary = [NSDictionary dictionaryWithObjectsAndKeys:pickedId, kW3ContactId, nil]; + + [peoplePicker pushViewController:personController animated:YES]; + } else { + // Retrieve and return pickedContact information + CDVContact* pickedContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)person]; + NSArray* fields = [picker.options objectForKey:@"fields"]; + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + picker.pickedContactDictionary = [pickedContact toDictionary:returnFields]; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:picker.pickedContactDictionary]; + [self.commandDelegate sendPluginResult:result callbackId:picker.callbackId]; + + if ([picker respondsToSelector:@selector(presentingViewController)]) { + [[picker presentingViewController] dismissViewControllerAnimated:YES completion:nil]; + } else { + [[picker parentViewController] dismissModalViewControllerAnimated:YES]; + } + } + return NO; +} + +- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker + shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier +{ + return YES; +} + +- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController*)peoplePicker +{ + // return contactId or invalid if none picked + CDVContactsPicker* picker = (CDVContactsPicker*)peoplePicker; + + if (picker.allowsEditing) { + // get the info after possible edit + // if we got this far, user has already approved/ disapproved addressBook access + ABAddressBookRef addrBook = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + if (&ABAddressBookCreateWithOptions != NULL) { + addrBook = ABAddressBookCreateWithOptions(NULL, NULL); + } else +#endif + { + // iOS 4 & 5 + addrBook = ABAddressBookCreate(); + } + ABRecordRef person = ABAddressBookGetPersonWithRecordID(addrBook, [[picker.pickedContactDictionary objectForKey:kW3ContactId] integerValue]); + if (person) { + CDVContact* pickedContact = [[CDVContact alloc] initFromABRecord:(ABRecordRef)person]; + NSArray* fields = [picker.options objectForKey:@"fields"]; + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + picker.pickedContactDictionary = [pickedContact toDictionary:returnFields]; + } + CFRelease(addrBook); + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:picker.pickedContactDictionary]; + [self.commandDelegate sendPluginResult:result callbackId:picker.callbackId]; + + if ([peoplePicker respondsToSelector:@selector(presentingViewController)]) { + [[peoplePicker presentingViewController] dismissViewControllerAnimated:YES completion:nil]; + } else { + [[peoplePicker parentViewController] dismissModalViewControllerAnimated:YES]; + } +} + +- (void)search:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSArray* fields = [command.arguments objectAtIndex:0]; + 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; + } + + 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; + } + } + } + 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]; + } + } + } + // 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; +} + +- (void)save:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + 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 + + [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; +} + +@end + +/* ABPersonViewController does not have any UI to dismiss. Adding navigationItems to it does not work properly + * The navigationItems are lost when the app goes into the background. The solution was to create an empty + * NavController in front of the ABPersonViewController. This will cause the ABPersonViewController to have a back button. By subclassing the ABPersonViewController, we can override viewDidDisappear and take down the entire NavigationController. + */ +@implementation CDVDisplayContactViewController +@synthesize contactsPlugin; + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + if ([self respondsToSelector:@selector(presentingViewController)]) { + [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil]; + } else { + [[self parentViewController] dismissModalViewControllerAnimated:YES]; + } +} + +@end +@implementation CDVAddressBookAccessError + +@synthesize errorCode; + +- (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code +{ + self = [super init]; + if (self) { + self.errorCode = code; + } + return self; +} + +@end + +@implementation CDVAddressBookHelper + +/** + * NOTE: workerBlock is responsible for releasing the addressBook that is passed to it + */ +- (void)createAddressBook:(CDVAddressBookWorkerBlock)workerBlock +{ + // TODO: this probably should be reworked - seems like the workerBlock can just create and release its own AddressBook, + // and also this important warning from (http://developer.apple.com/library/ios/#documentation/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/Chapters/BasicObjects.html): + // "Important: Instances of ABAddressBookRef cannot be used by multiple threads. Each thread must make its own instance." + ABAddressBookRef addressBook; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 + if (&ABAddressBookCreateWithOptions != NULL) { + CFErrorRef error = nil; + // CFIndex status = ABAddressBookGetAuthorizationStatus(); + 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 (), ^{ + if (error) { + workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + } else if (!granted) { + workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); + } else { + // access granted + workerBlock (addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + } + }); + }); + } else +#endif + { + // iOS 4 or 5 no checks needed + addressBook = ABAddressBookCreate (); + workerBlock (addressBook, NULL); + } +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVCordovaView.h b/iPhone/CordovaLib/Classes/CDVCordovaView.h new file mode 100755 index 0000000..6318b21 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCordovaView.h @@ -0,0 +1,23 @@ +/* + 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 <UIKit/UIKit.h> + +@interface CDVCordovaView : UIWebView {} +@end diff --git a/iPhone/CordovaLib/Classes/CDVCordovaView.m b/iPhone/CordovaLib/Classes/CDVCordovaView.m new file mode 100755 index 0000000..3dff80a --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVCordovaView.m @@ -0,0 +1,37 @@ +/* + 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 "CDVCordovaView.h" + +@implementation CDVCordovaView + +- (void)loadRequest:(NSURLRequest*)request +{ + [super loadRequest:request]; +} + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect { + // Drawing code. +} +*/ + +@end diff --git a/iPhone/CordovaLib/Classes/CDVDebug.h b/iPhone/CordovaLib/Classes/CDVDebug.h new file mode 100755 index 0000000..4a0d9f9 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVDebug.h @@ -0,0 +1,25 @@ +/* + 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. + */ + +#ifdef DEBUG + #define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#else + #define DLog(...) +#endif +#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) diff --git a/iPhone/CordovaLib/Classes/CDVDebugConsole.h b/iPhone/CordovaLib/Classes/CDVDebugConsole.h new file mode 100755 index 0000000..6a0a185 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVDebugConsole.h @@ -0,0 +1,28 @@ +/* + 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> +#import <UIKit/UIKit.h> +#import "CDVPlugin.h" + +@interface CDVDebugConsole : CDVPlugin {} + +- (void)log:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVDebugConsole.m b/iPhone/CordovaLib/Classes/CDVDebugConsole.m new file mode 100755 index 0000000..29cbb91 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVDebugConsole.m @@ -0,0 +1,37 @@ +/* + 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.h b/iPhone/CordovaLib/Classes/CDVDevice.h new file mode 100755 index 0000000..fd6ea12 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVDevice.h @@ -0,0 +1,30 @@ +/* + 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 <UIKit/UIKit.h> +#import "CDVPlugin.h" + +@interface CDVDevice : CDVPlugin +{} + ++ (NSString*)cordovaVersion; + +- (void)getDeviceInfo:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVDevice.m b/iPhone/CordovaLib/Classes/CDVDevice.m new file mode 100755 index 0000000..3fa4bb3 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVDevice.m @@ -0,0 +1,89 @@ +/* + 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 <sys/types.h> +#include <sys/sysctl.h> + +#import "CDV.h" + +@implementation UIDevice (ModelVersion) + +- (NSString*)modelVersion +{ + size_t size; + + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char* machine = malloc(size); + sysctlbyname("hw.machine", machine, &size, NULL, 0); + NSString* platform = [NSString stringWithUTF8String:machine]; + free(machine); + + return platform; +} + +@end + +@interface CDVDevice () {} +@end + +@implementation CDVDevice + +- (void)getDeviceInfo:(CDVInvokedUrlCommand*)command +{ + NSDictionary* deviceProperties = [self deviceProperties]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:deviceProperties]; + + /* Settings.plist + * Read the optional Settings.plist file and push these user-defined settings down into the web application. + * This can be useful for supplying build-time configuration variables down to the app to change its behavior, + * such as specifying Full / Lite version, or localization (English vs German, for instance). + */ + // TODO: turn this into an iOS only plugin + NSDictionary* temp = [CDVViewController getBundlePlist:@"Settings"]; + + if ([temp respondsToSelector:@selector(JSONString)]) { + NSString* js = [NSString stringWithFormat:@"window.Settings = %@;", [temp cdvjk_JSONString]]; + [self.commandDelegate evalJs:js]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (NSDictionary*)deviceProperties +{ + UIDevice* device = [UIDevice currentDevice]; + NSMutableDictionary* devProps = [NSMutableDictionary dictionaryWithCapacity:4]; + + [devProps setObject:[device modelVersion] forKey:@"model"]; + [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]; + return devReturn; +} + ++ (NSString*)cordovaVersion +{ + return CDV_VERSION; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVEcho.h b/iPhone/CordovaLib/Classes/CDVEcho.h new file mode 100755 index 0000000..76a4a96 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVEcho.h @@ -0,0 +1,23 @@ +/* + 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 "CDVPlugin.h" + +@interface CDVEcho : CDVPlugin +@end diff --git a/iPhone/CordovaLib/Classes/CDVEcho.m b/iPhone/CordovaLib/Classes/CDVEcho.m new file mode 100755 index 0000000..9cda957 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVEcho.m @@ -0,0 +1,44 @@ +/* + 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 "CDVEcho.h" +#import "CDV.h" + +@implementation CDVEcho + +- (void)echo:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[command.arguments objectAtIndex:0]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)echoAsyncHelper:(NSArray*)args +{ + [self.commandDelegate sendPluginResult:[args objectAtIndex:0] callbackId:[args objectAtIndex:1]]; +} + +- (void)echoAsync:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[command.arguments objectAtIndex:0]]; + + [self performSelector:@selector(echoAsyncHelper:) withObject:[NSArray arrayWithObjects:pluginResult, command.callbackId, nil] afterDelay:0]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVFile.h b/iPhone/CordovaLib/Classes/CDVFile.h new file mode 100755 index 0000000..204c9f0 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVFile.h @@ -0,0 +1,102 @@ +/* + 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> +#import "CDVPlugin.h" + +enum CDVFileError { + NOT_FOUND_ERR = 1, + SECURITY_ERR = 2, + ABORT_ERR = 3, + NOT_READABLE_ERR = 4, + ENCODING_ERR = 5, + NO_MODIFICATION_ALLOWED_ERR = 6, + INVALID_STATE_ERR = 7, + SYNTAX_ERR = 8, + INVALID_MODIFICATION_ERR = 9, + QUOTA_EXCEEDED_ERR = 10, + TYPE_MISMATCH_ERR = 11, + PATH_EXISTS_ERR = 12 +}; +typedef int CDVFileError; + +enum CDVFileSystemType { + TEMPORARY = 0, + PERSISTENT = 1 +}; +typedef int CDVFileSystemType; + +@interface CDVFile : CDVPlugin { + NSString* appDocsPath; + NSString* appLibraryPath; + NSString* appTempPath; + NSString* persistentPath; + NSString* temporaryPath; + + BOOL userHasAllowed; +} +- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath; +- (NSString*)getAppPath:(NSString*)pathFragment; +// -(NSString*) getFullPath: (NSString*)pathFragment; +- (void)requestFileSystem:(CDVInvokedUrlCommand*)command; +- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir; +- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command; +- (void)getDirectory:(CDVInvokedUrlCommand*)command; +- (void)getFile:(CDVInvokedUrlCommand*)command; +- (void)getParent:(CDVInvokedUrlCommand*)command; +- (void)getMetadata:(CDVInvokedUrlCommand*)command; +- (void)removeRecursively:(CDVInvokedUrlCommand*)command; +- (void)remove:(CDVInvokedUrlCommand*)command; +- (CDVPluginResult*)doRemove:(NSString*)fullPath; +- (void)copyTo:(CDVInvokedUrlCommand*)command; +- (void)moveTo:(CDVInvokedUrlCommand*)command; +- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest; +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy; +// - (void) toURI:(CDVInvokedUrlCommand*)command; +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command; +- (void)readEntries:(CDVInvokedUrlCommand*)command; + +- (void)readAsText:(CDVInvokedUrlCommand*)command; +- (void)readAsDataURL:(CDVInvokedUrlCommand*)command; +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath; +- (void)write:(CDVInvokedUrlCommand*)command; +- (void)testFileExists:(CDVInvokedUrlCommand*)command; +- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command; +// - (void) createDirectory:(CDVInvokedUrlCommand*)command; +// - (void) deleteDirectory:(CDVInvokedUrlCommand*)command; +// - (void) deleteFile:(CDVInvokedUrlCommand*)command; +- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command; +- (void)truncate:(CDVInvokedUrlCommand*)command; + +// - (BOOL) fileExists:(NSString*)fileName; +// - (BOOL) directoryExists:(NSString*)dirName; +- (void)writeToFile:(NSString*)fileName withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId; +- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos; + +@property (nonatomic, strong) NSString* appDocsPath; +@property (nonatomic, strong) NSString* appLibraryPath; +@property (nonatomic, strong) NSString* appTempPath; +@property (nonatomic, strong) NSString* persistentPath; +@property (nonatomic, strong) NSString* temporaryPath; +@property BOOL userHasAllowed; + +@end + +#define kW3FileTemporary @"temporary" +#define kW3FilePersistent @"persistent" diff --git a/iPhone/CordovaLib/Classes/CDVFile.m b/iPhone/CordovaLib/Classes/CDVFile.m new file mode 100755 index 0000000..1e5d009 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVFile.m @@ -0,0 +1,1145 @@ +/* + 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 "CDVFile.h" +#import "NSArray+Comparisons.h" +#import "NSDictionary+Extensions.h" +#import "JSONKit.h" +#import "NSData+Base64.h" +#import <MobileCoreServices/MobileCoreServices.h> +#import "CDVAvailability.h" +#import "sys/xattr.h" + +extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import)); + +#ifndef __IPHONE_5_1 + NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey"; +#endif + +@implementation CDVFile + +@synthesize appDocsPath, appLibraryPath, appTempPath, persistentPath, temporaryPath, userHasAllowed; + +- (id)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVFile*)[super initWithWebView:theWebView]; + if (self) { + // get the documents directory path + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + self.appDocsPath = [paths objectAtIndex:0]; + + paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + self.appLibraryPath = [paths objectAtIndex:0]; + + self.appTempPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() + + self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]]; + self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]]; + // NSLog(@"docs: %@ - temp: %@", self.appDocsPath, self.appTempPath); + } + + return self; +} + +- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath +{ + NSFileManager* fMgr = [[NSFileManager alloc] init]; + + NSError* __autoreleasing pError = nil; + + NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError]; + NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize]; + + return pNumAvail; +} + +// figure out if the pathFragment represents a persistent of temporary directory and return the full application path. +// returns nil if path is not persistent or temporary +- (NSString*)getAppPath:(NSString*)pathFragment +{ + NSString* appPath = nil; + NSRange rangeP = [pathFragment rangeOfString:self.persistentPath]; + NSRange rangeT = [pathFragment rangeOfString:self.temporaryPath]; + + if ((rangeP.location != NSNotFound) && (rangeT.location != NSNotFound)) { + // we found both in the path, return whichever one is first + if (rangeP.length < rangeT.length) { + appPath = self.appDocsPath; + } else { + appPath = self.appTempPath; + } + } else if (rangeP.location != NSNotFound) { + appPath = self.appDocsPath; + } else if (rangeT.location != NSNotFound) { + appPath = self.appTempPath; + } + return appPath; +} + +/* get the full path to this resource + * IN + * NSString* pathFragment - full Path from File or Entry object (includes system path info) + * OUT + * NSString* fullPath - full iOS path to this resource, nil if not found + */ + +/* Was here in order to NOT have to return full path, but W3C synchronous DirectoryEntry.toURI() killed that idea since I can't call into iOS to + * resolve full URI. Leaving this code here in case W3C spec changes. +-(NSString*) getFullPath: (NSString*)pathFragment +{ + return pathFragment; + NSString* fullPath = nil; + NSString *appPath = [ self getAppPath: pathFragment]; + if (appPath){ + + // remove last component from appPath + NSRange range = [appPath rangeOfString:@"/" options: NSBackwardsSearch]; + NSString* newPath = [appPath substringToIndex:range.location]; + // add pathFragment to get test Path + fullPath = [newPath stringByAppendingPathComponent:pathFragment]; + } + return fullPath; +} */ + +/* Request the File System info + * + * IN: + * arguments[0] - type (number as string) + * TEMPORARY = 0, PERSISTENT = 1; + * arguments[1] - size + * + * OUT: + * Dictionary representing FileSystem object + * name - the human readable directory name + * root = DirectoryEntry object + * bool isDirectory + * bool isFile + * string name + * string fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference !! + */ + +- (void)requestFileSystem:(CDVInvokedUrlCommand*)command +{ + NSArray* arguments = command.arguments; + + // arguments + NSString* strType = [arguments objectAtIndex:0]; + unsigned long long size = [[arguments objectAtIndex:1] longLongValue]; + + int type = [strType intValue]; + CDVPluginResult* result = nil; + + if (type > 1) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR]; + NSLog(@"iOS only supports TEMPORARY and PERSISTENT file systems"); + } else { + // NSString* fullPath = [NSString stringWithFormat:@"/%@", (type == 0 ? [self.appTempPath lastPathComponent] : [self.appDocsPath lastPathComponent])]; + NSString* fullPath = (type == 0 ? self.appTempPath : self.appDocsPath); + // check for avail space for size request + NSNumber* pNumAvail = [self checkFreeDiskSpace:fullPath]; + // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]); + if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; + } else { + NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; + [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]; + } + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* Creates a dictionary representing an Entry Object + * + * IN: + * NSString* fullPath of the entry + * FileSystem type + * BOOL isDirectory - YES if this is a directory, NO if is a file + * OUT: + * NSDictionary* + Entry object + * bool as NSNumber isDirectory + * bool as NSNumber isFile + * NSString* name - last part of path + * NSString* fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! + */ +- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir +{ + NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:4]; + NSString* lastPart = [fullPath lastPathComponent]; + + [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"]; + [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"]; + // NSURL* fileUrl = [NSURL fileURLWithPath:fullPath]; + // [dirEntry setObject: [fileUrl absoluteString] forKey: @"fullPath"]; + [dirEntry setObject:fullPath forKey:@"fullPath"]; + [dirEntry setObject:lastPart forKey:@"name"]; + + return dirEntry; +} + +/* + * Given a URI determine the File System information associated with it and return an appropriate W3C entry object + * IN + * NSString* fileURI - currently requires full file URI + * OUT + * Entry object + * bool isDirectory + * bool isFile + * string name + * string fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! + */ +- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* inputUri = [command.arguments objectAtIndex:0]; + + // don't know if string is encoded or not so unescape + NSString* cleanUri = [inputUri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + // now escape in order to create URL + NSString* strUri = [cleanUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSURL* testUri = [NSURL URLWithString:strUri]; + CDVPluginResult* result = nil; + + if (!testUri || ![testUri isFileURL]) { + // issue ENCODING_ERR + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; + } else { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSString* path = [testUri path]; + // NSLog(@"url path: %@", path); + BOOL isDir = NO; + // see if exists and is file or dir + BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir]; + if (bExists) { + // see if it contains docs path + NSRange range = [path rangeOfString:self.appDocsPath]; + NSString* foundFullPath = nil; + // there's probably an api or easier way to figure out the path type but I can't find it! + if ((range.location != NSNotFound) && (range.length == [self.appDocsPath length])) { + foundFullPath = self.appDocsPath; + } else { + // see if it contains the temp path + range = [path rangeOfString:self.appTempPath]; + if ((range.location != NSNotFound) && (range.length == [self.appTempPath length])) { + foundFullPath = self.appTempPath; + } + } + if (foundFullPath == nil) { + // error SECURITY_ERR - not one of the two paths types supported + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SECURITY_ERR]; + } else { + NSDictionary* fileSystem = [self getDirectoryEntry:path isDirectory:isDir]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } + } else { + // return NOT_FOUND_ERR + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + } + if (result != nil) { + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } +} + +/* Part of DirectoryEntry interface, creates or returns the specified directory + * IN: + * NSString* fullPath - full path for this directory + * NSString* path - directory to be created/returned; may be full path or relative path + * NSDictionary* - Flags object + * boolean as NSNumber create - + * if create is true and directory does not exist, create dir and return directory entry + * if create is true and exclusive is true and directory does exist, return error + * if create is false and directory does not exist, return error + * if create is false and the path represents a file, return error + * boolean as NSNumber exclusive - used in conjunction with create + * if exclusive is true and create is true - specifies failure if directory already exists + * + * + */ +- (void)getDirectory:(CDVInvokedUrlCommand*)command +{ + NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments]; + NSMutableDictionary* options = nil; + + if ([arguments count] >= 3) { + options = [arguments objectAtIndex:2 withDefault:nil]; + } + // add getDir to options and call getFile() + if (options != nil) { + options = [NSMutableDictionary dictionaryWithDictionary:options]; + } else { + options = [NSMutableDictionary dictionaryWithCapacity:1]; + } + [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"]; + if ([arguments count] >= 3) { + [arguments replaceObjectAtIndex:2 withObject:options]; + } else { + [arguments addObject:options]; + } + CDVInvokedUrlCommand* subCommand = + [[CDVInvokedUrlCommand alloc] initWithArguments:arguments + callbackId:command.callbackId + className:command.className + methodName:command.methodName]; + + [self getFile:subCommand]; +} + +/* Part of DirectoryEntry interface, creates or returns the specified file + * IN: + * NSString* fullPath - full path for this file + * NSString* path - file to be created/returned; may be full path or relative path + * NSDictionary* - Flags object + * boolean as NSNumber create - + * if create is true and file does not exist, create file and return File entry + * if create is true and exclusive is true and file does exist, return error + * if create is false and file does not exist, return error + * if create is false and the path represents a directory, return error + * boolean as NSNumber exclusive - used in conjunction with create + * if exclusive is true and create is true - specifies failure if file already exists + * + * + */ +- (void)getFile:(CDVInvokedUrlCommand*)command +{ + // arguments are URL encoded + NSString* fullPath = [command.arguments objectAtIndex:0]; + NSString* requestedPath = [command.arguments objectAtIndex:1]; + NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil]; + + CDVPluginResult* result = nil; + BOOL bDirRequest = NO; + BOOL create = NO; + BOOL exclusive = NO; + int errorCode = 0; // !!! risky - no error code currently defined for 0 + + if ([options valueForKeyIsNumber:@"create"]) { + create = [(NSNumber*)[options valueForKey:@"create"] boolValue]; + } + if ([options valueForKeyIsNumber:@"exclusive"]) { + exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue]; + } + + if ([options valueForKeyIsNumber:@"getDir"]) { + // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method + bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue]; + } + // see if the requested path has invalid characters - should we be checking for more than just ":"? + if ([requestedPath rangeOfString:@":"].location != NSNotFound) { + errorCode = ENCODING_ERR; + } else { + // was full or relative path provided? + NSRange range = [requestedPath rangeOfString:fullPath]; + BOOL bIsFullPath = range.location != NSNotFound; + + NSString* reqFullPath = nil; + + if (!bIsFullPath) { + reqFullPath = [fullPath stringByAppendingPathComponent:requestedPath]; + } else { + reqFullPath = requestedPath; + } + + // NSLog(@"reqFullPath = %@", reqFullPath); + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir; + BOOL bExists = [fileMgr fileExistsAtPath:reqFullPath isDirectory:&bIsDir]; + if (bExists && (create == NO) && (bIsDir == !bDirRequest)) { + // path exists and is of requested type - return TYPE_MISMATCH_ERR + errorCode = TYPE_MISMATCH_ERR; + } else if (!bExists && (create == NO)) { + // path does not exist and create is false - return NOT_FOUND_ERR + errorCode = NOT_FOUND_ERR; + } else if (bExists && (create == YES) && (exclusive == YES)) { + // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR + errorCode = PATH_EXISTS_ERR; + } else { + // if bExists and create == YES - just return data + // if bExists and create == NO - just return data + // if !bExists and create == YES - create and return data + BOOL bSuccess = YES; + NSError __autoreleasing* pError = nil; + if (!bExists && (create == YES)) { + if (bDirRequest) { + // create the dir + bSuccess = [fileMgr createDirectoryAtPath:reqFullPath withIntermediateDirectories:NO attributes:nil error:&pError]; + } else { + // create the empty file + bSuccess = [fileMgr createFileAtPath:reqFullPath contents:nil attributes:nil]; + } + } + if (!bSuccess) { + errorCode = ABORT_ERR; + if (pError) { + NSLog(@"error creating directory: %@", [pError localizedDescription]); + } + } else { + // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]); + // file existed or was created + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:reqFullPath isDirectory:bDirRequest]]; + } + } // are all possible conditions met? + } + + if (errorCode > 0) { + // create error callback + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * Look up the parent Entry containing this Entry. + * If this Entry is the root of its filesystem, its parent is itself. + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * NSMutableDictionary* options + * empty + */ +- (void)getParent:(CDVInvokedUrlCommand*)command +{ + // arguments are URL encoded + NSString* fullPath = [command.arguments objectAtIndex:0]; + + CDVPluginResult* result = nil; + NSString* newPath = nil; + + if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + // return self + newPath = fullPath; + } else { + // since this call is made from an existing Entry object - the parent should already exist so no additional error checking + // remove last component and return Entry + NSRange range = [fullPath rangeOfString:@"/" options:NSBackwardsSearch]; + newPath = [fullPath substringToIndex:range.location]; + } + + if (newPath) { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir; + BOOL bExists = [fileMgr fileExistsAtPath:newPath isDirectory:&bIsDir]; + if (bExists) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newPath isDirectory:bIsDir]]; + } + } + if (!result) { + // invalid path or file does not exist + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * get MetaData of entry + * Currently MetaData only includes modificationTime. + */ +- (void)getMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + NSString* testPath = argPath; // [self getFullPath: argPath]; + + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing error = nil; + CDVPluginResult* result = nil; + + NSDictionary* fileAttribs = [fileMgr attributesOfItemAtPath:testPath error:&error]; + + if (fileAttribs) { + NSDate* modDate = [fileAttribs fileModificationDate]; + if (modDate) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[modDate timeIntervalSince1970] * 1000]; + } + } else { + // didn't get fileAttribs + CDVFileError errorCode = ABORT_ERR; + NSLog(@"error getting metadata: %@", [error localizedDescription]); + if ([error code] == NSFileNoSuchFileError) { + errorCode = NOT_FOUND_ERR; + } + // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode]; + } + if (!result) { + // invalid path or file does not exist + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * set MetaData of entry + * Currently we only support "com.apple.MobileBackup" (boolean) + */ +- (void)setMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* filePath = [command.arguments objectAtIndex:0]; + NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:nil]; + + CDVPluginResult* result = nil; + BOOL ok = NO; + + // we only care about this iCloud key for now. + // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute) + NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup"; + id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey]; + + if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) { + if (IsAtLeastiOSVersion(@"5.1")) { + NSURL* url = [NSURL fileURLWithPath:filePath]; + NSError* __autoreleasing error = nil; + + ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error]; + } else { // below 5.1 (deprecated - only really supported in 5.01) + u_int8_t value = [iCloudBackupExtendedAttributeValue intValue]; + if (value == 0) { // remove the attribute (allow backup, the default) + ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0); + } else { // set the attribute (skip backup) + ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0); + } + } + } + + if (ok) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* removes the directory or file entry + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * + * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir + * returns INVALID_MODIFICATION_ERR if is dir and is not empty + * returns NOT_FOUND_ERR if file or dir is not found +*/ +- (void)remove:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + + CDVPluginResult* result = nil; + CDVFileError errorCode = 0; // !! 0 not currently defined + + // error if try to remove top level (documents or tmp) dir + if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + errorCode = NO_MODIFICATION_ALLOWED_ERR; + } else { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir = NO; + BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; + if (!bExists) { + errorCode = NOT_FOUND_ERR; + } + if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fullPath error:nil] count] != 0)) { + // dir is not empty + errorCode = INVALID_MODIFICATION_ERR; + } + } + if (errorCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } else { + // perform actual remove + result = [self doRemove:fullPath]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* recursively removes the directory + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * + * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir + * returns NOT_FOUND_ERR if file or dir is not found + */ +- (void)removeRecursively:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + + CDVPluginResult* result = nil; + + // error if try to remove top level (documents or tmp) dir + if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + } else { + result = [self doRemove:fullPath]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* remove the file or directory (recursively) + * IN: + * NSString* fullPath - the full path to the file or directory to be removed + * NSString* callbackId + * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling + */ + +- (CDVPluginResult*)doRemove:(NSString*)fullPath +{ + CDVPluginResult* result = nil; + BOOL bSuccess = NO; + NSError* __autoreleasing pError = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + + @try { + bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError]; + if (bSuccess) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + // see if we can give a useful error + CDVFileError errorCode = ABORT_ERR; + NSLog(@"error getting metadata: %@", [pError localizedDescription]); + if ([pError code] == NSFileNoSuchFileError) { + errorCode = NOT_FOUND_ERR; + } else if ([pError code] == NSFileWriteNoPermissionError) { + errorCode = NO_MODIFICATION_ALLOWED_ERR; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + } @catch(NSException* e) { // NSInvalidArgumentException if path is . or .. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR]; + } + + return result; +} + +- (void)copyTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:YES]; +} + +- (void)moveTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:NO]; +} + +/** + * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name, + * or attempted to copy a directory into a directory that it contains directly or indirectly. + * + * IN: + * NSString* srcDir + * NSString* destinationDir + * OUT: + * YES copy/ move is allows + * NO move is onto itself + */ +- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest +{ + // This weird test is to determine if we are copying or moving a directory into itself. + // Copy /Documents/myDir to /Documents/myDir-backup is okay but + // Copy /Documents/myDir to /Documents/myDir/backup not okay + BOOL copyOK = YES; + NSRange range = [dest rangeOfString:src]; + + if (range.location != NSNotFound) { + NSRange testRange = {range.length - 1, ([dest length] - range.length)}; + NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange]; + if (resultRange.location != NSNotFound) { + copyOK = NO; + } + } + return copyOK; +} + +/* Copy/move a file or directory to a new location + * IN: + * NSArray* arguments + * 0 - NSString* fullPath of entry + * 1 - NSString* newName the new name of the entry, defaults to the current name + * NSMutableDictionary* options - DirectoryEntry to which to copy the entry + * BOOL - bCopy YES if copy, NO if move + * + */ +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy +{ + NSArray* arguments = command.arguments; + + // arguments + NSString* srcFullPath = [arguments objectAtIndex:0]; + NSString* destRootPath = [arguments objectAtIndex:1]; + // optional argument + NSString* newName = ([arguments count] > 2) ? [arguments objectAtIndex:2] : [srcFullPath lastPathComponent]; // use last component from appPath if new name not provided + + CDVPluginResult* result = nil; + CDVFileError errCode = 0; // !! Currently 0 is not defined, use this to signal error !! + + /*NSString* destRootPath = nil; + NSString* key = @"fullPath"; + if([options valueForKeyIsString:key]){ + destRootPath = [options objectForKey:@"fullPath"]; + }*/ + + if (!destRootPath) { + // no destination provided + errCode = NOT_FOUND_ERR; + } else if ([newName rangeOfString:@":"].location != NSNotFound) { + // invalid chars in new name + errCode = ENCODING_ERR; + } else { + NSString* newFullPath = [destRootPath stringByAppendingPathComponent:newName]; + if ([newFullPath isEqualToString:srcFullPath]) { + // source and destination can not be the same + errCode = INVALID_MODIFICATION_ERR; + } else { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + + BOOL bSrcIsDir = NO; + BOOL bDestIsDir = NO; + BOOL bNewIsDir = NO; + BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir]; + BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir]; + BOOL bNewExists = [fileMgr fileExistsAtPath:newFullPath isDirectory:&bNewIsDir]; + if (!bSrcExists || !bDestExists) { + // the source or the destination root does not exist + errCode = NOT_FOUND_ERR; + } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) { + // can't copy/move dir to file + errCode = INVALID_MODIFICATION_ERR; + } else { // no errors yet + NSError* __autoreleasing error = nil; + BOOL bSuccess = NO; + if (bCopy) { + if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath] /*[newFullPath hasPrefix:srcFullPath]*/) { + // can't copy dir into self + errCode = INVALID_MODIFICATION_ERR; + } else if (bNewExists) { + // the full destination should NOT already exist if a copy + errCode = PATH_EXISTS_ERR; + } else { + bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFullPath error:&error]; + } + } else { // move + // iOS requires that destination must not exist before calling moveTo + // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents + // + if (!bSrcIsDir && (bNewExists && bNewIsDir)) { + // can't move a file to directory + errCode = INVALID_MODIFICATION_ERR; + } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath]) { // [newFullPath hasPrefix:srcFullPath]){ + // can't move a dir into itself + errCode = INVALID_MODIFICATION_ERR; + } else if (bNewExists) { + if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFullPath error:NULL] count] != 0)) { + // can't move dir to a dir that is not empty + errCode = INVALID_MODIFICATION_ERR; + newFullPath = nil; // so we won't try to move + } else { + // remove destination so can perform the moveItemAtPath + bSuccess = [fileMgr removeItemAtPath:newFullPath error:NULL]; + if (!bSuccess) { + errCode = INVALID_MODIFICATION_ERR; // is this the correct error? + newFullPath = nil; + } + } + } else if (bNewIsDir && [newFullPath hasPrefix:srcFullPath]) { + // can't move a directory inside itself or to any child at any depth; + errCode = INVALID_MODIFICATION_ERR; + newFullPath = nil; + } + + if (newFullPath != nil) { + bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFullPath error:&error]; + } + } + if (bSuccess) { + // should verify it is there and of the correct type??? + NSDictionary* newEntry = [self getDirectoryEntry:newFullPath isDirectory:bSrcIsDir]; // should be the same type as source + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry]; + } else { + errCode = INVALID_MODIFICATION_ERR; // catch all + if (error) { + if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) { + errCode = NOT_READABLE_ERR; + } else if ([error code] == NSFileWriteOutOfSpaceError) { + errCode = QUOTA_EXCEEDED_ERR; + } else if ([error code] == NSFileWriteNoPermissionError) { + errCode = NO_MODIFICATION_ALLOWED_ERR; + } + } + } + } + } + } + if (errCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* return the URI to the entry + * IN: + * NSArray* arguments + * 0 - NSString* fullPath of entry + * 1 - desired mime type of entry - ignored - always returns file:// + */ + +/* Not needed since W3C toURI is synchronous. Leaving code here for now in case W3C spec changes..... +- (void) toURI:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* argPath = [command.arguments objectAtIndex:0]; + PluginResult* result = nil; + NSString* jsString = nil; + + NSString* fullPath = [self getFullPath: argPath]; + if (fullPath) { + // do we need to make sure the file actually exists? + // create file uri + NSString* strUri = [fullPath stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; + NSURL* fileUrl = [NSURL fileURLWithPath:strUri]; + if (fileUrl) { + result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: [fileUrl absoluteString]]; + jsString = [result toSuccessCallbackString:callbackId]; + } // else NOT_FOUND_ERR + } + if(!jsString) { + // was error + result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt: NOT_FOUND_ERR cast: @"window.localFileSystem._castError"]; + jsString = [result toErrorCallbackString:callbackId]; + } + + [self writeJavascript:jsString]; +}*/ +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + CDVPluginResult* result = nil; + + NSString* fullPath = argPath; // [self getFullPath: argPath]; + + if (fullPath) { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir = NO; + // make sure it exists and is not a directory + BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; + if (!bExists || bIsDir) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } else { + // create dictionary of file info + NSError* __autoreleasing error = nil; + NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error]; + NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; + [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; + [fileInfo setObject:argPath forKey:@"fullPath"]; + [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping + [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"]; + NSDate* modDate = [fileAttrs fileModificationDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + } + } + if (!result) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_INSTANTIATION_EXCEPTION]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)readEntries:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + + CDVPluginResult* result = nil; + + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing error = nil; + NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fullPath error:&error]; + + if (contents) { + NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1]; + if ([contents count] > 0) { + // create an Entry (as JSON) for each file/dir + for (NSString* name in contents) { + // see if is dir or file + NSString* entryPath = [fullPath stringByAppendingPathComponent:name]; + BOOL bIsDir = NO; + [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir]; + NSDictionary* entryDict = [self getDirectoryEntry:entryPath isDirectory:bIsDir]; + [entries addObject:entryDict]; + } + } + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries]; + } else { + // assume not found but could check error for more specific error conditions + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* read and return file data + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* encoding - NOT USED, iOS reads and writes using UTF8! + */ +- (void)readAsText:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + // NSString* encoding = [command.arguments objectAtIndex:2]; // not currently used + CDVPluginResult* result = nil; + + NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; + + if (!file) { + // invalid path entry + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } else { + NSData* readData = [file readDataToEndOfFile]; + + [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 = @""; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[pNStrBuff stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + [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 + * + * 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]; + + CDVFileError errCode = ABORT_ERR; + CDVPluginResult* result = nil; + + if (!argPath) { + errCode = SYNTAX_ERR; + } else { + NSString* mimeType = [self getMimeTypeFromPath:argPath]; + if (!mimeType) { + // can't return as data URL if can't figure out the mimeType + errCode = ENCODING_ERR; + } else { + NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; + NSData* readData = [file readDataToEndOfFile]; + [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; + } + } + } + if (!result) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + // NSLog(@"readAsDataURL return: %@", jsString); + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* helper function to get the mimeType from the file extension + * IN: + * NSString* fullPath - filename (may include path) + * OUT: + * NSString* the mime type as type/subtype. nil if not able to determine + */ +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath +{ + NSString* mimeType = nil; + + if (fullPath) { + CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL); + if (typeId) { + mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType); + if (!mimeType) { + // special case for m4a + if ([(__bridge NSString*) typeId rangeOfString:@"m4a-audio"].location != NSNotFound) { + mimeType = @"audio/mp4"; + } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) { + mimeType = @"audio/wav"; + } + } + CFRelease(typeId); + } + } + return mimeType; +} + +- (void)truncate:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + unsigned long long pos = (unsigned long long)[[command.arguments objectAtIndex:1] longLongValue]; + + NSString* appFile = argPath; // [self getFullPath:argPath]; + + unsigned long long newPos = [self truncateFile:appFile atPosition:pos]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:newPos]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos +{ + unsigned long long newPos = 0UL; + + NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath]; + + if (file) { + [file truncateFileAtOffset:(unsigned long long)pos]; + newPos = [file offsetInFile]; + [file synchronizeFile]; + [file closeFile]; + } + return newPos; +} + +/* write + * IN: + * NSArray* arguments + * 0 - NSString* file path to write to + * 1 - NSString* data to write + * 2 - NSNumber* position to begin writing + */ +- (void)write:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSArray* arguments = command.arguments; + + // arguments + NSString* argPath = [arguments objectAtIndex:0]; + NSString* argData = [arguments objectAtIndex:1]; + unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue]; + + NSString* fullPath = argPath; // [self getFullPath:argPath]; + + [self truncateFile:fullPath atPosition:pos]; + + [self writeToFile:fullPath withData:argData append:YES callback:callbackId]; +} + +- (void)writeToFile:(NSString*)filePath withData:(NSString*)data 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]; + if (fileStream) { + NSUInteger len = [encData length]; + [fileStream open]; + + bytesWritten = [fileStream write:[encData bytes] maxLength:len]; + + [fileStream close]; + if (bytesWritten > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten]; + // } else { + // can probably get more detailed error info via [fileStream streamError] + // errCode already set to INVALID_MODIFICATION_ERR; + // bytesWritten = 0; // may be set to -1 on error + } + } // else fileStream not created return INVALID_MODIFICATION_ERR + } else { + // invalid filePath + errCode = NOT_FOUND_ERR; + } + if (!result) { + // was an error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)testFileExists:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + // Get the file manager + NSFileManager* fMgr = [NSFileManager defaultManager]; + NSString* appFile = argPath; // [ self getFullPath: argPath]; + + BOOL bExists = [fMgr fileExistsAtPath:appFile]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1:0)]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + // Get the file manager + NSFileManager* fMgr = [[NSFileManager alloc] init]; + NSString* appFile = argPath; // [self getFullPath: argPath]; + BOOL bIsDir = NO; + BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir]; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1:0)]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +// Returns number of bytes available via callback +- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command +{ + // no arguments + + NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath]; + + NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]]; + // NSLog(@"Free space is %@", strFreeSpace ); + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.h b/iPhone/CordovaLib/Classes/CDVFileTransfer.h new file mode 100755 index 0000000..76c6a95 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.h @@ -0,0 +1,71 @@ +/* + 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> +#import "CDVPlugin.h" + +enum CDVFileTransferError { + FILE_NOT_FOUND_ERR = 1, + INVALID_URL_ERR = 2, + CONNECTION_ERR = 3, + CONNECTION_ABORTED = 4 +}; +typedef int CDVFileTransferError; + +enum CDVFileTransferDirection { + CDV_TRANSFER_UPLOAD = 1, + CDV_TRANSFER_DOWNLOAD = 2, +}; +typedef int CDVFileTransferDirection; + +// Magic value within the options dict used to set a cookie. +extern NSString* const kOptionsKeyCookie; + +@interface CDVFileTransfer : CDVPlugin {} + +- (void)upload:(CDVInvokedUrlCommand*)command; +- (void)download:(CDVInvokedUrlCommand*)command; +- (NSString*)escapePathComponentForUrlString:(NSString*)urlString; + +// Visible for testing. +- (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData:(NSData*)fileData; +- (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target; + +- (NSMutableDictionary*)createFileTransferError:(int)code + AndSource :(NSString*)source + AndTarget :(NSString*)target + AndHttpStatus :(int)httpStatus; +@property (readonly) NSMutableDictionary* activeTransfers; +@end + +@interface CDVFileTransferDelegate : NSObject {} + +@property (strong) NSMutableData* responseData; // atomic +@property (nonatomic, strong) CDVFileTransfer* command; +@property (nonatomic, assign) CDVFileTransferDirection direction; +@property (nonatomic, strong) NSURLConnection* connection; +@property (nonatomic, copy) NSString* callbackId; +@property (nonatomic, copy) NSString* objectId; +@property (nonatomic, copy) NSString* source; +@property (nonatomic, copy) NSString* target; +@property (assign) int responseCode; // atomic +@property (nonatomic, assign) NSInteger bytesTransfered; +@property (nonatomic, assign) NSInteger bytesExpected; + +@end; diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.m b/iPhone/CordovaLib/Classes/CDVFileTransfer.m new file mode 100755 index 0000000..b25177a --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.m @@ -0,0 +1,575 @@ +/* + 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 "CDV.h" + +#include <CFNetwork/CFNetwork.h> + +@interface CDVFileTransfer () +// Sets the requests headers for the request. +- (void)applyRequestHeaders:(NSDictionary*)headers toRequest:(NSMutableURLRequest*)req; +// Creates a delegate to handle an upload. +- (CDVFileTransferDelegate*)delegateForUploadCommand:(CDVInvokedUrlCommand*)command; +// Creates an NSData* for the file for the given upload arguments. +- (NSData*)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command; +@end + +// Buffer size to use for streaming uploads. +static const NSUInteger kStreamBufferSize = 32768; +// Magic value within the options dict used to set a cookie. +NSString* const kOptionsKeyCookie = @"__cookie"; +// Form boundary for multi-part requests. +NSString* const kFormBoundary = @"+++++org.apache.cordova.formBoundary"; + +// Writes the given data to the stream in a blocking way. +// If successful, returns bytesToWrite. +// If the stream was closed on the other end, returns 0. +// If there was an error, returns -1. +static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) +{ + UInt8* bytes = (UInt8*)[data bytes]; + NSUInteger bytesToWrite = [data length]; + NSUInteger totalBytesWritten = 0; + + while (totalBytesWritten < bytesToWrite) { + CFIndex result = CFWriteStreamWrite(stream, + bytes + totalBytesWritten, + bytesToWrite - totalBytesWritten); + if (result < 0) { + CFStreamError error = CFWriteStreamGetError(stream); + NSLog(@"WriteStreamError domain: %ld error: %ld", error.domain, error.error); + return result; + } else if (result == 0) { + return result; + } + totalBytesWritten += result; + } + + return totalBytesWritten; +} + +@implementation CDVFileTransfer +@synthesize activeTransfers; + +- (NSString*)escapePathComponentForUrlString:(NSString*)urlString +{ + NSRange schemeAndHostRange = [urlString rangeOfString:@"://.*?/" options:NSRegularExpressionSearch]; + + if (schemeAndHostRange.length == 0) { + return urlString; + } + + NSInteger schemeAndHostEndIndex = NSMaxRange(schemeAndHostRange); + NSString* schemeAndHost = [urlString substringToIndex:schemeAndHostEndIndex]; + NSString* pathComponent = [urlString substringFromIndex:schemeAndHostEndIndex]; + pathComponent = [pathComponent stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + return [schemeAndHost stringByAppendingString:pathComponent]; +} + +- (void)applyRequestHeaders:(NSDictionary*)headers toRequest:(NSMutableURLRequest*)req +{ + [req setValue:@"XMLHttpRequest" forHTTPHeaderField:@"X-Requested-With"]; + + NSString* userAgent = [[self.webView request] valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent) { + [req setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + + for (NSString* headerName in headers) { + id value = [headers objectForKey:headerName]; + if (!value || (value == [NSNull null])) { + value = @"null"; + } + + // First, remove an existing header if one exists. + [req setValue:nil forHTTPHeaderField:headerName]; + + if (![value isKindOfClass:[NSArray class]]) { + value = [NSArray arrayWithObject:value]; + } + + // Then, append all header values. + for (id __strong subValue in value) { + // Convert from an NSNumber -> NSString. + if ([subValue respondsToSelector:@selector(stringValue)]) { + subValue = [subValue stringValue]; + } + if ([subValue isKindOfClass:[NSString class]]) { + [req addValue:subValue forHTTPHeaderField:headerName]; + } + } + } +} + +- (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData:(NSData*)fileData +{ + // arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode] + // however, params is a JavaScript object and during marshalling is put into the options dict, + // thus debug and chunkedMode are the 6th and 7th arguments + NSArray* arguments = command.arguments; + NSString* target = (NSString*)[arguments objectAtIndex:0]; + NSString* server = (NSString*)[arguments objectAtIndex:1]; + NSString* fileKey = [arguments objectAtIndex:2 withDefault:@"file"]; + NSString* fileName = [arguments objectAtIndex:3 withDefault:@"no-filename"]; + NSString* mimeType = [arguments objectAtIndex:4 withDefault:nil]; + NSDictionary* options = [arguments objectAtIndex:5 withDefault:nil]; + // NSString* trustAllHosts = (NSString*)[arguments objectAtIndex:6]; // allow self-signed certs + BOOL chunkedMode = [[arguments objectAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; + NSDictionary* headers = [arguments objectAtIndex:8 withDefault:nil]; + + // CFStreamCreateBoundPair crashes on iOS < 5. + if (!IsAtLeastiOSVersion(@"5")) { + // TODO(agrieve): See if it's okay license-wise to include the work-around code from: + // http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Listings/PostController_m.html + chunkedMode = NO; + } + + CDVPluginResult* result = nil; + CDVFileTransferError errorCode = 0; + + // NSURL does not accepts URLs with spaces in the path. We escape the path in order + // to be more lenient. + NSURL* url = [NSURL URLWithString:server]; + + if (!url) { + errorCode = INVALID_URL_ERR; + NSLog(@"File Transfer Error: Invalid server URL %@", server); + } else if (!fileData) { + errorCode = FILE_NOT_FOUND_ERR; + } + + if (errorCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:errorCode AndSource:target AndTarget:server]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return nil; + } + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; + [req setHTTPMethod:@"POST"]; + + // Magic value to set a cookie + if ([options objectForKey:kOptionsKeyCookie]) { + [req setValue:[options objectForKey:kOptionsKeyCookie] forHTTPHeaderField:@"Cookie"]; + [req setHTTPShouldHandleCookies:NO]; + } + + NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kFormBoundary]; + [req setValue:contentType forHTTPHeaderField:@"Content-Type"]; + [self applyRequestHeaders:headers toRequest:req]; + + NSData* formBoundaryData = [[NSString stringWithFormat:@"--%@\r\n", kFormBoundary] dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData* postBodyBeforeFile = [NSMutableData data]; + + for (NSString* key in options) { + id val = [options objectForKey:key]; + if (!val || (val == [NSNull null]) || [key isEqualToString:kOptionsKeyCookie]) { + continue; + } + // if it responds to stringValue selector (eg NSNumber) get the NSString + if ([val respondsToSelector:@selector(stringValue)]) { + val = [val stringValue]; + } + // finally, check whether it is a NSString (for dataUsingEncoding selector below) + if (![val isKindOfClass:[NSString class]]) { + continue; + } + + [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:formBoundaryData]; + [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fileKey, fileName] dataUsingEncoding:NSUTF8StringEncoding]]; + if (mimeType != nil) { + [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]]; + } + [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Length: %d\r\n\r\n", [fileData length]] dataUsingEncoding:NSUTF8StringEncoding]]; + + DLog(@"fileData length: %d", [fileData length]); + NSData* postBodyAfterFile = [[NSString stringWithFormat:@"\r\n--%@--\r\n", kFormBoundary] dataUsingEncoding:NSUTF8StringEncoding]; + + NSUInteger totalPayloadLength = [postBodyBeforeFile length] + [fileData length] + [postBodyAfterFile length]; + [req setValue:[[NSNumber numberWithInteger:totalPayloadLength] stringValue] forHTTPHeaderField:@"Content-Length"]; + + if (chunkedMode) { + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + CFStreamCreateBoundPair(NULL, &readStream, &writeStream, kStreamBufferSize); + [req setHTTPBodyStream:CFBridgingRelease(readStream)]; + + [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; + } + } + } else { + NSLog (@"FileTransfer: Failed to open writeStream"); + } + CFWriteStreamClose (writeStream); + CFRelease (writeStream); + }]; + } else { + [postBodyBeforeFile appendData:fileData]; + [postBodyBeforeFile appendData:postBodyAfterFile]; + [req setHTTPBody:postBodyBeforeFile]; + } + return req; +} + +- (CDVFileTransferDelegate*)delegateForUploadCommand:(CDVInvokedUrlCommand*)command +{ + NSString* source = [command.arguments objectAtIndex:0]; + NSString* server = [command.arguments objectAtIndex:1]; + NSString* objectId = [command.arguments objectAtIndex:9]; + + CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init]; + + delegate.command = self; + delegate.callbackId = command.callbackId; + delegate.direction = CDV_TRANSFER_UPLOAD; + delegate.objectId = objectId; + delegate.source = source; + delegate.target = server; + return delegate; +} + +- (NSData*)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command +{ + NSString* target = (NSString*)[command.arguments objectAtIndex:0]; + NSError* __autoreleasing err = nil; + // Extract the path part out of a file: URL. + NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path]; + + // 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); + } + return fileData; +} + +- (void)upload:(CDVInvokedUrlCommand*)command +{ + // fileData and req are split into helper functions to ease the unit testing of delegateForUpload. + NSData* fileData = [self fileDataForUploadCommand:command]; + NSURLRequest* req = [self requestForUploadCommand:command fileData:fileData]; + + if (req == nil) { + return; + } + CDVFileTransferDelegate* delegate = [self delegateForUploadCommand:command]; + [NSURLConnection connectionWithRequest:req delegate:delegate]; + + if (activeTransfers == nil) { + activeTransfers = [[NSMutableDictionary alloc] init]; + } + + [activeTransfers setObject:delegate forKey:delegate.objectId]; +} + +- (void)abort:(CDVInvokedUrlCommand*)command +{ + NSString* objectId = [command.arguments objectAtIndex:0]; + + 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]; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]]; + [self.commandDelegate sendPluginResult:result callbackId:delegate.callbackId]; + } +} + +- (void)download:(CDVInvokedUrlCommand*)command +{ + DLog(@"File Transfer downloading file..."); + NSString* sourceUrl = [command.arguments objectAtIndex:0]; + NSString* filePath = [command.arguments objectAtIndex:1]; + // NSString* trustAllHosts = (NSString*)[arguments objectAtIndex:6]; // allow self-signed certs + NSString* objectId = [command.arguments objectAtIndex:3]; + CDVPluginResult* result = nil; + CDVFileTransferError errorCode = 0; + + NSURL* file; + + if ([filePath hasPrefix:@"/"]) { + file = [NSURL fileURLWithPath:filePath]; + } else { + file = [NSURL URLWithString:filePath]; + } + + NSURL* url = [NSURL URLWithString:sourceUrl]; + + if (!url) { + errorCode = INVALID_URL_ERR; + NSLog(@"File Transfer Error: Invalid server URL %@", sourceUrl); + } else if (![file isFileURL]) { + errorCode = FILE_NOT_FOUND_ERR; + NSLog(@"File Transfer Error: Invalid file path or URL %@", filePath); + } + + if (errorCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:errorCode AndSource:sourceUrl AndTarget:filePath]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; + [self applyRequestHeaders:nil toRequest:req]; + + CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init]; + delegate.command = self; + delegate.direction = CDV_TRANSFER_DOWNLOAD; + delegate.callbackId = command.callbackId; + delegate.objectId = objectId; + delegate.source = sourceUrl; + delegate.target = filePath; + + delegate.connection = [NSURLConnection connectionWithRequest:req delegate:delegate]; + + if (activeTransfers == nil) { + activeTransfers = [[NSMutableDictionary alloc] init]; + } + + [activeTransfers setObject:delegate forKey:delegate.objectId]; +} + +- (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target +{ + NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:3]; + + [result setObject:[NSNumber numberWithInt:code] forKey:@"code"]; + [result setObject:source forKey:@"source"]; + [result setObject:target forKey:@"target"]; + NSLog(@"FileTransferError %@", result); + + return result; +} + +- (NSMutableDictionary*)createFileTransferError:(int)code + AndSource:(NSString*)source + AndTarget:(NSString*)target + AndHttpStatus:(int)httpStatus +{ + NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:4]; + + [result setObject:[NSNumber numberWithInt:code] forKey:@"code"]; + [result setObject:source forKey:@"source"]; + [result setObject:target forKey:@"target"]; + [result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"]; + NSLog(@"FileTransferError %@", result); + + return result; +} + +- (void)onReset +{ + for (CDVFileTransferDelegate* delegate in [activeTransfers allValues]) { + [delegate.connection cancel]; + } + + [activeTransfers removeAllObjects]; +} + +@end + +@implementation CDVFileTransferDelegate + +@synthesize callbackId, connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId; + +- (void)connectionDidFinishLoading:(NSURLConnection*)connection +{ + NSString* uploadResponse = nil; + BOOL downloadResponse; + NSMutableDictionary* uploadResult; + CDVPluginResult* result = nil; + NSError* __autoreleasing error = nil; + NSString* parentPath; + BOOL bDirRequest = NO; + CDVFile* file; + + NSLog(@"File Transfer Finished with response code %d", self.responseCode); + + if (self.direction == CDV_TRANSFER_UPLOAD) { + if ((self.responseCode >= 200) && (self.responseCode < 300)) { + // create dictionary to return FileUploadResult object + uploadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + uploadResult = [NSMutableDictionary dictionaryWithCapacity:3]; + if (uploadResponse != nil) { + [uploadResult setObject:uploadResponse forKey:@"response"]; + } + [uploadResult setObject:[NSNumber numberWithInt:self.bytesTransfered] forKey:@"bytesSent"]; + [uploadResult setObject:[NSNumber numberWithInt:self.responseCode] forKey:@"responseCode"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:uploadResult]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + } + } + if (self.direction == CDV_TRANSFER_DOWNLOAD) { + DLog(@"Write file %@", target); + // error=[[NSError alloc]init]; + + 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]; + } + + downloadResponse = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error]; + + if (downloadResponse == NO) { + // send our results back + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + } 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 + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + } + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + } + } + + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; + + // remove connection for activeTransfers + [command.activeTransfers removeObjectForKey:objectId]; +} + +- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response +{ + // required for iOS 4.3, for some reason; response is + // a plain NSURLResponse, not the HTTP subclass + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + self.responseCode = 403; + self.bytesExpected = [response expectedContentLength]; + return; + } + + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; + + self.responseCode = [httpResponse statusCode]; + self.bytesExpected = [response expectedContentLength]; +} + +- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error +{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + + NSLog(@"File Transfer Error: %@", [error localizedDescription]); + + // remove connection for activeTransfers + [command.activeTransfers removeObjectForKey:objectId]; + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data +{ + self.bytesTransfered += data.length; + [self.responseData appendData:data]; + + if (self.direction == CDV_TRANSFER_DOWNLOAD) { + BOOL lengthComputable = (self.bytesExpected != NSURLResponseUnknownLength); + NSMutableDictionary* downloadProgress = [NSMutableDictionary dictionaryWithCapacity:3]; + [downloadProgress setObject:[NSNumber numberWithBool:lengthComputable] forKey:@"lengthComputable"]; + [downloadProgress setObject:[NSNumber numberWithInt:self.bytesTransfered] forKey:@"loaded"]; + [downloadProgress setObject:[NSNumber numberWithInt:self.bytesExpected] forKey:@"total"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:downloadProgress]; + [result setKeepCallbackAsBool:true]; + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +- (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite +{ + if (self.direction == CDV_TRANSFER_UPLOAD) { + NSMutableDictionary* uploadProgress = [NSMutableDictionary dictionaryWithCapacity:3]; + + [uploadProgress setObject:[NSNumber numberWithBool:true] forKey:@"lengthComputable"]; + [uploadProgress setObject:[NSNumber numberWithInt:totalBytesWritten] forKey:@"loaded"]; + [uploadProgress setObject:[NSNumber numberWithInt:totalBytesExpectedToWrite] forKey:@"total"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:uploadProgress]; + [result setKeepCallbackAsBool:true]; + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + self.bytesTransfered = totalBytesWritten; +} + +/* TESTING ONLY CODE +// use ONLY for testing with self signed certificates +// uncomment and modify server name in connection didReceiveAuthenticationChallenge +- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace +{ + return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; +} + +- (void)connection:(NSURLConnection *) connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge +{ + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) + { + //NSLog(@"challenge host: %@", challenge.protectionSpace.host); + // we only trust our own domain + if ([challenge.protectionSpace.host isEqualToString:@"serverName.domain.com"]){ + NSURLCredential* myCredential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]; + + [challenge.sender useCredential:myCredential forAuthenticationChallenge:challenge]; + + } + } + + [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; +} +// uncomment the above two methods for testing servers with self signed certificates +// END TESTING ONLY CODE + */ +- (id)init +{ + if ((self = [super init])) { + self.responseData = [NSMutableData data]; + } + return self; +} + +@end; diff --git a/iPhone/CordovaLib/Classes/CDVGlobalization.h b/iPhone/CordovaLib/Classes/CDVGlobalization.h new file mode 100755 index 0000000..0384656 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVGlobalization.h @@ -0,0 +1,150 @@ +/* + 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> +#import "CDVPlugin.h" + +#define CDV_FORMAT_SHORT 0 +#define CDV_FORMAT_MEDIUM 1 +#define CDV_FORMAT_LONG 2 +#define CDV_FORMAT_FULL 3 +#define CDV_SELECTOR_MONTHS 0 +#define CDV_SELECTOR_DAYS 1 + +enum CDVGlobalizationError { + CDV_UNKNOWN_ERROR = 0, + CDV_FORMATTING_ERROR = 1, + CDV_PARSING_ERROR = 2, + CDV_PATTERN_ERROR = 3, +}; +typedef NSUInteger CDVGlobalizationError; + +@interface CDVGlobalization : CDVPlugin { + CFLocaleRef currentLocale; +} + +- (void)getPreferredLanguage:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns the string identifier for the clients current locale setting. + * It returns the locale identifier string to the successCB callback with a + * properties object as a parameter. If there is an error getting the locale, + * then the errorCB callback is invoked. + */ +- (void)getLocaleName:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns a date formatted as a string according to the clients user preferences and + * calendar using the time zone of the client. It returns the formatted date string to the + * successCB callback with a properties object as a parameter. If there is an error + * formatting the date, then the errorCB callback is invoked. + * + * options: "date" contains the number of milliseconds that represents the JavaScript date + */ +- (void)dateToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Parses a date formatted as a string according to the clients user + * preferences and calendar using the time zone of the client and returns + * the corresponding date object. It returns the date to the successCB + * callback with a properties object as a parameter. If there is an error + * parsing the date string, then the errorCB callback is invoked. + * + * options: "dateString" contains the JavaScript string to parse for a date + */ +- (void)stringToDate:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns a pattern string for formatting and parsing dates according to the clients + * user preferences. It returns the pattern to the successCB callback with a + * properties object as a parameter. If there is an error obtaining the pattern, + * then the errorCB callback is invoked. + * + */ +- (void)getDatePattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns an array of either the names of the months or days of the week + * according to the clients user preferences and calendar. It returns the array of names to the + * successCB callback with a properties object as a parameter. If there is an error obtaining the + * names, then the errorCB callback is invoked. + * + */ +- (void)getDateNames:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns whether daylight savings time is in effect for a given date using the clients + * time zone and calendar. It returns whether or not daylight savings time is in effect + * to the successCB callback with a properties object as a parameter. If there is an error + * reading the date, then the errorCB callback is invoked. + * + * options: "date" contains the number of milliseconds that represents the JavaScript date + * + */ +- (void)isDayLightSavingsTime:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns the first day of the week according to the clients user preferences and calendar. + * The days of the week are numbered starting from 1 where 1 is considered to be Sunday. + * It returns the day to the successCB callback with a properties object as a parameter. + * If there is an error obtaining the pattern, then the errorCB callback is invoked. + * + */ +- (void)getFirstDayOfWeek:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns a number formatted as a string according to the clients user preferences. + * It returns the formatted number string to the successCB callback with a properties object as a + * parameter. If there is an error formatting the number, then the errorCB callback is invoked. + * + * options: "number" contains the JavaScript number to format + * + */ +- (void)numberToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Parses a number formatted as a string according to the clients user preferences and + * returns the corresponding number. It returns the number to the successCB callback with a + * properties object as a parameter. If there is an error parsing the number string, then + * the errorCB callback is invoked. + * + * options: "numberString" contains the JavaScript string to parse for a number + * + */ +- (void)stringToNumber:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns a pattern string for formatting and parsing numbers according to the clients user + * preferences. It returns the pattern to the successCB callback with a properties object as a + * parameter. If there is an error obtaining the pattern, then the errorCB callback is invoked. + * + */ +- (void)getNumberPattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +/** + * Returns a pattern string for formatting and parsing currency values according to the clients + * user preferences and ISO 4217 currency code. It returns the pattern to the successCB callback with a + * properties object as a parameter. If there is an error obtaining the pattern, then the errorCB + * callback is invoked. + * + * options: "currencyCode" contains the ISO currency code from JavaScript + */ +- (void)getCurrencyPattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVGlobalization.m b/iPhone/CordovaLib/Classes/CDVGlobalization.m new file mode 100755 index 0000000..4d960cd --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVGlobalization.m @@ -0,0 +1,790 @@ +/* + 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 "CDVGlobalization.h" + +@implementation CDVGlobalization + +- (id)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVGlobalization*)[super initWithWebView:theWebView]; + if (self) { + currentLocale = CFLocaleCopyCurrent(); + } + return self; +} + +- (void)getPreferredLanguage:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString* callbackId = [arguments objectAtIndex:0]; + CDVPluginResult* result = nil; + + NSLog(@"log1"); + // Source: http://stackoverflow.com/questions/3910244/getting-current-device-language-in-ios + // (should be OK) + NSString* language = [[NSLocale preferredLanguages] objectAtIndex:0]; + + if (language) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:language forKey:@"value"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:dictionary]; + } else { + // TBD is this ever expected to happen? + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_UNKNOWN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unknown error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)getLocaleName:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callbackId = [arguments objectAtIndex:0]; + NSDictionary* dictionary = nil; + + NSLocale* locale = [NSLocale currentLocale]; + + if (locale) { + dictionary = [NSDictionary dictionaryWithObject:[locale localeIdentifier] forKey:@"value"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_UNKNOWN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unknown error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)dateToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CFDateFormatterStyle style = kCFDateFormatterShortStyle; + CFDateFormatterStyle dateStyle = kCFDateFormatterShortStyle; + CFDateFormatterStyle timeStyle = kCFDateFormatterShortStyle; + NSDate* date = nil; + NSString* dateString = nil; + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + + id milliseconds = [options valueForKey:@"date"]; + + if (milliseconds && [milliseconds isKindOfClass:[NSNumber class]]) { + // get the number of seconds since 1970 and create the date object + date = [NSDate dateWithTimeIntervalSince1970:[milliseconds doubleValue] / 1000]; + } + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired format length + if ([key isEqualToString:@"formatLength"]) { + if ([item isEqualToString:@"short"]) { + style = kCFDateFormatterShortStyle; + } else if ([item isEqualToString:@"medium"]) { + style = kCFDateFormatterMediumStyle; + } else if ([item isEqualToString:@"long"]) { + style = kCFDateFormatterLongStyle; + } else if ([item isEqualToString:@"full"]) { + style = kCFDateFormatterFullStyle; + } + } + // get the type of date and time to generate + else if ([key isEqualToString:@"selector"]) { + if ([item isEqualToString:@"date"]) { + dateStyle = style; + timeStyle = kCFDateFormatterNoStyle; + } else if ([item isEqualToString:@"time"]) { + dateStyle = kCFDateFormatterNoStyle; + timeStyle = style; + } else if ([item isEqualToString:@"date and time"]) { + dateStyle = style; + timeStyle = style; + } + } + } + } + } + + // create the formatter using the user's current default locale and formats for dates and times + CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, + 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); + } + + // if the date was converted to a string successfully then return the result + if (dateString) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:dateString forKey:@"value"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + // DLog(@"GlobalizationCommand dateToString unable to format %@", [date description]); + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_FORMATTING_ERROR] forKey:@"code"]; + [dictionary setValue:@"Formatting error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)stringToDate:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CFDateFormatterStyle style = kCFDateFormatterShortStyle; + CFDateFormatterStyle dateStyle = kCFDateFormatterShortStyle; + CFDateFormatterStyle timeStyle = kCFDateFormatterShortStyle; + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + NSString* dateString = nil; + NSDateComponents* comps = nil; + + // get the string that is to be parsed for a date + id ms = [options valueForKey:@"dateString"]; + + if (ms && [ms isKindOfClass:[NSString class]]) { + dateString = ms; + } + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired format length + if ([key isEqualToString:@"formatLength"]) { + if ([item isEqualToString:@"short"]) { + style = kCFDateFormatterShortStyle; + } else if ([item isEqualToString:@"medium"]) { + style = kCFDateFormatterMediumStyle; + } else if ([item isEqualToString:@"long"]) { + style = kCFDateFormatterLongStyle; + } else if ([item isEqualToString:@"full"]) { + style = kCFDateFormatterFullStyle; + } + } + // get the type of date and time to generate + else if ([key isEqualToString:@"selector"]) { + if ([item isEqualToString:@"date"]) { + dateStyle = style; + timeStyle = kCFDateFormatterNoStyle; + } else if ([item isEqualToString:@"time"]) { + dateStyle = kCFDateFormatterNoStyle; + timeStyle = style; + } else if ([item isEqualToString:@"date and time"]) { + dateStyle = style; + timeStyle = style; + } + } + } + } + } + + // get the user's default settings for date and time formats + CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, + 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); + + // if we were able to parse the date then get the date and time components + if (date != NULL) { + NSCalendar* calendar = [NSCalendar currentCalendar]; + + unsigned unitFlags = NSYearCalendarUnit | + NSMonthCalendarUnit | + NSDayCalendarUnit | + NSHourCalendarUnit | + NSMinuteCalendarUnit | + NSSecondCalendarUnit; + + comps = [calendar components:unitFlags fromDate:(__bridge NSDate*)date]; + CFRelease(date); + } + + // put the various elements of the date and time into a dictionary + if (comps != nil) { + NSArray* keys = [NSArray arrayWithObjects:@"year", @"month", @"day", @"hour", @"minute", @"second", @"millisecond", nil]; + NSArray* values = [NSArray arrayWithObjects:[NSNumber numberWithInt:[comps year]], + [NSNumber numberWithInt:[comps month] - 1], + [NSNumber numberWithInt:[comps day]], + [NSNumber numberWithInt:[comps hour]], + [NSNumber numberWithInt:[comps minute]], + [NSNumber numberWithInt:[comps second]], + [NSNumber numberWithInt:0], /* iOS does not provide milliseconds */ + nil]; + + NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + // Dlog(@"GlobalizationCommand stringToDate unable to parse %@", dateString); + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_PARSING_ERROR] forKey:@"code"]; + [dictionary setValue:@"unable to parse" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)getDatePattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CFDateFormatterStyle style = kCFDateFormatterShortStyle; + CFDateFormatterStyle dateStyle = kCFDateFormatterShortStyle; + CFDateFormatterStyle timeStyle = kCFDateFormatterShortStyle; + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired format length + if ([key isEqualToString:@"formatLength"]) { + if ([item isEqualToString:@"short"]) { + style = kCFDateFormatterShortStyle; + } else if ([item isEqualToString:@"medium"]) { + style = kCFDateFormatterMediumStyle; + } else if ([item isEqualToString:@"long"]) { + style = kCFDateFormatterLongStyle; + } else if ([item isEqualToString:@"full"]) { + style = kCFDateFormatterFullStyle; + } + } + // get the type of date and time to generate + else if ([key isEqualToString:@"selector"]) { + if ([item isEqualToString:@"date"]) { + dateStyle = style; + timeStyle = kCFDateFormatterNoStyle; + } else if ([item isEqualToString:@"time"]) { + dateStyle = kCFDateFormatterNoStyle; + timeStyle = style; + } else if ([item isEqualToString:@"date and time"]) { + dateStyle = style; + timeStyle = style; + } + } + } + } + } + + // get the user's default settings for date and time formats + CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, + currentLocale, + dateStyle, + timeStyle); + + // get the date pattern to apply when formatting and parsing + CFStringRef datePattern = CFDateFormatterGetFormat(formatter); + // get the user's current time zone information + CFTimeZoneRef timezone = (CFTimeZoneRef)CFDateFormatterCopyProperty(formatter, kCFDateFormatterTimeZone); + + // put the pattern and time zone information into the dictionary + if ((datePattern != nil) && (timezone != nil)) { + NSArray* keys = [NSArray arrayWithObjects:@"pattern", @"timezone", @"utc_offset", @"dst_offset", nil]; + NSArray* values = [NSArray arrayWithObjects:((__bridge NSString*)datePattern), + [((__bridge NSTimeZone*)timezone)abbreviation], + [NSNumber numberWithLong:[((__bridge NSTimeZone*)timezone)secondsFromGMT]], + [NSNumber numberWithDouble:[((__bridge NSTimeZone*)timezone)daylightSavingTimeOffset]], + nil]; + + NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_PATTERN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Pattern error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + if (timezone) { + CFRelease(timezone); + } + CFRelease(formatter); +} + +- (void)getDateNames:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + int style = CDV_FORMAT_LONG; + int selector = CDV_SELECTOR_MONTHS; + CFStringRef dataStyle = kCFDateFormatterMonthSymbols; + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired type of name + if ([key isEqualToString:@"type"]) { + if ([item isEqualToString:@"narrow"]) { + style = CDV_FORMAT_SHORT; + } else if ([item isEqualToString:@"wide"]) { + style = CDV_FORMAT_LONG; + } + } + // determine if months or days are needed + else if ([key isEqualToString:@"item"]) { + if ([item isEqualToString:@"months"]) { + selector = CDV_SELECTOR_MONTHS; + } else if ([item isEqualToString:@"days"]) { + selector = CDV_SELECTOR_DAYS; + } + } + } + } + } + + CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, + currentLocale, + kCFDateFormatterFullStyle, + kCFDateFormatterFullStyle); + + if ((selector == CDV_SELECTOR_MONTHS) && (style == CDV_FORMAT_LONG)) { + dataStyle = kCFDateFormatterMonthSymbols; + } else if ((selector == CDV_SELECTOR_MONTHS) && (style == CDV_FORMAT_SHORT)) { + dataStyle = kCFDateFormatterShortMonthSymbols; + } else if ((selector == CDV_SELECTOR_DAYS) && (style == CDV_FORMAT_LONG)) { + dataStyle = kCFDateFormatterWeekdaySymbols; + } else if ((selector == CDV_SELECTOR_DAYS) && (style == CDV_FORMAT_SHORT)) { + dataStyle = kCFDateFormatterShortWeekdaySymbols; + } + + CFArrayRef names = (CFArrayRef)CFDateFormatterCopyProperty(formatter, dataStyle); + + if (names) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:((__bridge NSArray*)names) forKey:@"value"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + CFRelease(names); + } + // error + else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_UNKNOWN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unknown error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)isDayLightSavingsTime:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSDate* date = nil; + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + + id milliseconds = [options valueForKey:@"date"]; + + if (milliseconds && [milliseconds isKindOfClass:[NSNumber class]]) { + // get the number of seconds since 1970 and create the date object + date = [NSDate dateWithTimeIntervalSince1970:[milliseconds doubleValue] / 1000]; + } + + if (date) { + // get the current calendar for the user and check if the date is using DST + NSCalendar* calendar = [NSCalendar currentCalendar]; + NSTimeZone* timezone = [calendar timeZone]; + NSNumber* dst = [NSNumber numberWithBool:[timezone isDaylightSavingTimeForDate:date]]; + + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:dst forKey:@"dst"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_UNKNOWN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unknown error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; +} + +- (void)getFirstDayOfWeek:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + + NSCalendar* calendar = [NSCalendar autoupdatingCurrentCalendar]; + + NSNumber* day = [NSNumber numberWithInt:[calendar firstWeekday]]; + + if (day) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:day forKey:@"value"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_UNKNOWN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unknown error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; +} + +- (void)numberToString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + CFNumberFormatterStyle style = kCFNumberFormatterDecimalStyle; + NSNumber* number = nil; + + id value = [options valueForKey:@"number"]; + + if (value && [value isKindOfClass:[NSNumber class]]) { + number = (NSNumber*)value; + } + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired style of formatting + if ([key isEqualToString:@"type"]) { + if ([item isEqualToString:@"percent"]) { + style = kCFNumberFormatterPercentStyle; + } else if ([item isEqualToString:@"currency"]) { + style = kCFNumberFormatterCurrencyStyle; + } else if ([item isEqualToString:@"decimal"]) { + style = kCFNumberFormatterDecimalStyle; + } + } + } + } + } + + CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, + currentLocale, + style); + + // get the localized string based upon the locale and user preferences + NSString* numberString = (__bridge_transfer NSString*)CFNumberFormatterCreateStringWithNumber(kCFAllocatorDefault, + formatter, + (__bridge CFNumberRef)number); + + if (numberString) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:numberString forKey:@"value"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + // DLog(@"GlobalizationCommand numberToString unable to format %@", [number stringValue]); + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_FORMATTING_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unable to format" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)stringToNumber:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + CFNumberFormatterStyle style = kCFNumberFormatterDecimalStyle; + NSString* numberString = nil; + double doubleValue; + + id value = [options valueForKey:@"numberString"]; + + if (value && [value isKindOfClass:[NSString class]]) { + numberString = (NSString*)value; + } + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired style of formatting + if ([key isEqualToString:@"type"]) { + if ([item isEqualToString:@"percent"]) { + style = kCFNumberFormatterPercentStyle; + } else if ([item isEqualToString:@"currency"]) { + style = kCFNumberFormatterCurrencyStyle; + } else if ([item isEqualToString:@"decimal"]) { + style = kCFNumberFormatterDecimalStyle; + } + } + } + } + } + + CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, + 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) { + CFNumberFormatterSetProperty(formatter, kCFNumberFormatterIsLenient, kCFBooleanTrue); + } + + // parse againist the largest type to avoid data loss + Boolean rc = CFNumberFormatterGetValueFromString(formatter, + (__bridge CFStringRef)numberString, + NULL, + kCFNumberDoubleType, + &doubleValue); + + if (rc) { + NSDictionary* dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:doubleValue] forKey:@"value"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + // DLog(@"GlobalizationCommand stringToNumber unable to parse %@", numberString); + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_PARSING_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unable to parse" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)getNumberPattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + CFNumberFormatterStyle style = kCFNumberFormatterDecimalStyle; + CFStringRef symbolType = NULL; + NSString* symbol = @""; + + // see if any options have been specified + id items = [options valueForKey:@"options"]; + + if (items && [items isKindOfClass:[NSMutableDictionary class]]) { + NSEnumerator* enumerator = [items keyEnumerator]; + id key; + + // iterate through all the options + while ((key = [enumerator nextObject])) { + id item = [items valueForKey:key]; + + // make sure that only string values are present + if ([item isKindOfClass:[NSString class]]) { + // get the desired style of formatting + if ([key isEqualToString:@"type"]) { + if ([item isEqualToString:@"percent"]) { + style = kCFNumberFormatterPercentStyle; + } else if ([item isEqualToString:@"currency"]) { + style = kCFNumberFormatterCurrencyStyle; + } else if ([item isEqualToString:@"decimal"]) { + style = kCFNumberFormatterDecimalStyle; + } + } + } + } + } + + CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, + currentLocale, + style); + + NSString* numberPattern = (__bridge NSString*)CFNumberFormatterGetFormat(formatter); + + if (style == kCFNumberFormatterCurrencyStyle) { + symbolType = kCFNumberFormatterCurrencySymbol; + } else if (style == kCFNumberFormatterPercentStyle) { + symbolType = kCFNumberFormatterPercentSymbol; + } + + if (symbolType) { + symbol = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, symbolType); + } + + NSString* decimal = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterDecimalSeparator); + NSString* grouping = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterGroupingSeparator); + NSString* posSign = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterPlusSign); + NSString* negSign = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterMinusSign); + NSNumber* fracDigits = (__bridge_transfer NSNumber*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterMinFractionDigits); + NSNumber* roundingDigits = (__bridge_transfer NSNumber*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterRoundingIncrement); + + // put the pattern information into the dictionary + if (numberPattern != nil) { + NSArray* keys = [NSArray arrayWithObjects:@"pattern", @"symbol", @"fraction", @"rounding", + @"positive", @"negative", @"decimal", @"grouping", nil]; + NSArray* values = [NSArray arrayWithObjects:numberPattern, symbol, fracDigits, roundingDigits, + posSign, negSign, decimal, grouping, nil]; + NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + } + // error + else { + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_PATTERN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Pattern error" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; + + CFRelease(formatter); +} + +- (void)getCurrencyPattern:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + CDVPluginResult* result = nil; + NSString* callBackId = [arguments objectAtIndex:0]; + NSString* currencyCode = nil; + NSString* numberPattern = nil; + NSString* decimal = nil; + NSString* grouping = nil; + int32_t defaultFractionDigits; + double roundingIncrement; + Boolean rc; + + id value = [options valueForKey:@"currencyCode"]; + + if (value && [value isKindOfClass:[NSString class]]) { + currencyCode = (NSString*)value; + } + + // first see if there is base currency info available and fill in the currency_info structure + rc = CFNumberFormatterGetDecimalInfoForCurrencyCode((__bridge CFStringRef)currencyCode, &defaultFractionDigits, &roundingIncrement); + + // now set the currency code in the formatter + if (rc) { + CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, + currentLocale, + kCFNumberFormatterCurrencyStyle); + + CFNumberFormatterSetProperty(formatter, kCFNumberFormatterCurrencyCode, (__bridge CFStringRef)currencyCode); + CFNumberFormatterSetProperty(formatter, kCFNumberFormatterInternationalCurrencySymbol, (__bridge CFStringRef)currencyCode); + + numberPattern = (__bridge NSString*)CFNumberFormatterGetFormat(formatter); + decimal = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterCurrencyDecimalSeparator); + grouping = (__bridge_transfer NSString*)CFNumberFormatterCopyProperty(formatter, kCFNumberFormatterCurrencyGroupingSeparator); + + NSArray* keys = [NSArray arrayWithObjects:@"pattern", @"code", @"fraction", @"rounding", + @"decimal", @"grouping", nil]; + NSArray* values = [NSArray arrayWithObjects:numberPattern, currencyCode, [NSNumber numberWithInt:defaultFractionDigits], + [NSNumber numberWithDouble:roundingIncrement], decimal, grouping, nil]; + NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:values forKeys:keys]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + CFRelease(formatter); + } + // error + else { + // DLog(@"GlobalizationCommand getCurrencyPattern unable to get pattern for %@", currencyCode); + NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:2]; + [dictionary setValue:[NSNumber numberWithInt:CDV_PATTERN_ERROR] forKey:@"code"]; + [dictionary setValue:@"Unable to get pattern" forKey:@"message"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + } + + [self.commandDelegate sendPluginResult:result callbackId:callBackId]; +} + +- (void)dealloc +{ + if (currentLocale) { + CFRelease(currentLocale); + currentLocale = nil; + } +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h new file mode 100755 index 0000000..51199ed --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h @@ -0,0 +1,72 @@ +/* + 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 "CDVPlugin.h" +#import "CDVInvokedUrlCommand.h" +#import "CDVScreenOrientationDelegate.h" + +@class CDVInAppBrowserViewController; + +@protocol CDVInAppBrowserNavigationDelegate <NSObject> + +- (void)browserLoadStart:(NSURL*)url; +- (void)browserLoadStop:(NSURL*)url; +- (void)browserExit; + +@end + +@interface CDVInAppBrowser : CDVPlugin <CDVInAppBrowserNavigationDelegate>{} + +@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; +@property (nonatomic, copy) NSString* callbackId; + +- (void)open:(CDVInvokedUrlCommand*)command; +- (void)close:(CDVInvokedUrlCommand*)command; + +@end + +@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{} + +@property (nonatomic, strong) IBOutlet UIWebView* webView; +@property (nonatomic, strong) IBOutlet UIBarButtonItem* closeButton; +@property (nonatomic, strong) IBOutlet UILabel* addressLabel; +@property (nonatomic, strong) IBOutlet UIBarButtonItem* backButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem* forwardButton; +@property (nonatomic, strong) IBOutlet UIActivityIndicatorView* spinner; +@property (nonatomic, strong) IBOutlet UIToolbar* toolbar; + +@property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate; +@property (nonatomic, weak) id <CDVInAppBrowserNavigationDelegate> navigationDelegate; +@property (nonatomic, strong) NSString* userAgent; + +- (void)close; +- (void)navigateTo:(NSURL*)url; +- (void)showLocationBar:(BOOL)show; + +- (id)initWithUserAgent:(NSString*)userAgent; + +@end + +@interface CDVInAppBrowserOptions : NSObject {} + +@property (nonatomic, assign) BOOL location; + ++ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m new file mode 100755 index 0000000..22fd1fc --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m @@ -0,0 +1,494 @@ +/* + 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 "CDVInAppBrowser.h" +#import "CDVPluginResult.h" +#import "CDVViewController.h" + +#define kInAppBrowserTargetSelf @"_self" +#define kInAppBrowserTargetSystem @"_system" +#define kInAppBrowserTargetBlank @"_blank" + +#define TOOLBAR_HEIGHT 44.0 +#define LOCATIONBAR_HEIGHT 21.0 +#define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT)) + +#pragma mark CDVInAppBrowser + +@implementation CDVInAppBrowser + +- (CDVInAppBrowser*)initWithWebView:(UIWebView*)theWebView +{ + self = [super initWithWebView:theWebView]; + if (self != nil) { + // your initialization here + } + + return self; +} + +- (void)onReset +{ + [self close:nil]; +} + +- (void)close:(CDVInvokedUrlCommand*)command +{ + if (self.inAppBrowserViewController != nil) { + [self.inAppBrowserViewController close]; + self.inAppBrowserViewController = nil; + } + + self.callbackId = nil; +} + +- (void)open:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult; + + NSString* url = [command argumentAtIndex:0]; + NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf]; + NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]]; + + self.callbackId = command.callbackId; + + if (url != nil) { + NSURL* baseUrl = [self.webView.request URL]; + NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL]; + if ([target isEqualToString:kInAppBrowserTargetSelf]) { + [self openInCordovaWebView:absoluteUrl withOptions:options]; + } else if ([target isEqualToString:kInAppBrowserTargetSystem]) { + [self openInSystem:absoluteUrl]; + } else { // _blank or anything else + [self openInInAppBrowser:absoluteUrl withOptions:options]; + } + + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"]; + } + + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options +{ + if (self.inAppBrowserViewController == nil) { + NSString* originalUA = [CDVViewController originalUserAgent]; + self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA]; + self.inAppBrowserViewController.navigationDelegate = self; + + if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) { + self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController; + } + } + + CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options]; + [self.inAppBrowserViewController showLocationBar:browserOptions.location]; + + if (self.viewController.modalViewController != self.inAppBrowserViewController) { + [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; + } + [self.inAppBrowserViewController navigateTo:url]; +} + +- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options +{ + BOOL passesWhitelist = YES; + + if ([self.viewController isKindOfClass:[CDVViewController class]]) { + CDVViewController* vc = (CDVViewController*)self.viewController; + if ([vc.whitelist schemeIsAllowed:[url scheme]]) { + passesWhitelist = [vc.whitelist URLIsAllowed:url]; + } + } else { // something went wrong, we can't get the whitelist + passesWhitelist = NO; + } + + if (passesWhitelist) { + NSURLRequest* request = [NSURLRequest requestWithURL:url]; + [self.webView loadRequest:request]; + } else { // this assumes the InAppBrowser can be excepted from the white-list + [self openInInAppBrowser:url withOptions:options]; + } +} + +- (void)openInSystem:(NSURL*)url +{ + if ([[UIApplication sharedApplication] canOpenURL:url]) { + [[UIApplication sharedApplication] openURL:url]; + } else { // handle any custom schemes to plugins + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + } +} + +#pragma mark CDVInAppBrowserNavigationDelegate + +- (void)browserLoadStart:(NSURL*)url +{ + if (self.callbackId != nil) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:@ {@"type":@"loadstart", @"url":[url absoluteString]}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } +} + +- (void)browserLoadStop:(NSURL*)url +{ + if (self.callbackId != nil) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:@ {@"type":@"loadstop", @"url":[url absoluteString]}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } +} + +- (void)browserExit +{ + if (self.callbackId != nil) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:@ {@"type":@"exit"}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } +} + +@end + +#pragma mark CDVInAppBrowserViewController + +@implementation CDVInAppBrowserViewController + +- (id)initWithUserAgent:(NSString*)userAgent +{ + self = [super init]; + if (self != nil) { + self.userAgent = userAgent; + [self createViews]; + } + + return self; +} + +- (void)createViews +{ + // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included + + CGRect webViewBounds = self.view.bounds; + + webViewBounds.size.height -= FOOTER_HEIGHT; + + if (!self.webView) { + // setting the UserAgent must occur before the UIWebView is instantiated. + // This is read per instantiation, so it does not affect the main Cordova UIWebView + NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:self.userAgent, @"UserAgent", nil]; + [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; + + self.webView = [[UIWebView alloc] initWithFrame:webViewBounds]; + self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + [self.view addSubview:self.webView]; + [self.view sendSubviewToBack:self.webView]; + + self.webView.delegate = self; + self.webView.scalesPageToFit = TRUE; + self.webView.backgroundColor = [UIColor whiteColor]; + + self.webView.clearsContextBeforeDrawing = YES; + self.webView.clipsToBounds = YES; + self.webView.contentMode = UIViewContentModeScaleToFill; + self.webView.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); + self.webView.multipleTouchEnabled = YES; + self.webView.opaque = YES; + self.webView.scalesPageToFit = NO; + self.webView.userInteractionEnabled = YES; + } + + self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + self.spinner.alpha = 1.000; + self.spinner.autoresizesSubviews = YES; + self.spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin; + self.spinner.clearsContextBeforeDrawing = NO; + self.spinner.clipsToBounds = NO; + self.spinner.contentMode = UIViewContentModeScaleToFill; + self.spinner.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); + self.spinner.frame = CGRectMake(454.0, 231.0, 20.0, 20.0); + self.spinner.hidden = YES; + self.spinner.hidesWhenStopped = YES; + self.spinner.multipleTouchEnabled = NO; + self.spinner.opaque = NO; + self.spinner.userInteractionEnabled = NO; + [self.spinner stopAnimating]; + + 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]; + + UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil]; + fixedSpaceButton.width = 20; + + self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, (self.view.bounds.size.height - TOOLBAR_HEIGHT), self.view.bounds.size.width, TOOLBAR_HEIGHT)]; + self.toolbar.alpha = 1.000; + self.toolbar.autoresizesSubviews = YES; + self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + self.toolbar.barStyle = UIBarStyleBlackOpaque; + self.toolbar.clearsContextBeforeDrawing = NO; + self.toolbar.clipsToBounds = NO; + self.toolbar.contentMode = UIViewContentModeScaleToFill; + self.toolbar.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); + self.toolbar.hidden = NO; + self.toolbar.multipleTouchEnabled = NO; + self.toolbar.opaque = NO; + self.toolbar.userInteractionEnabled = YES; + + CGFloat labelInset = 5.0; + self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, (self.view.bounds.size.height - FOOTER_HEIGHT), self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)]; + self.addressLabel.adjustsFontSizeToFitWidth = NO; + self.addressLabel.alpha = 1.000; + self.addressLabel.autoresizesSubviews = YES; + self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin; + self.addressLabel.backgroundColor = [UIColor clearColor]; + self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; + self.addressLabel.clearsContextBeforeDrawing = YES; + self.addressLabel.clipsToBounds = YES; + self.addressLabel.contentMode = UIViewContentModeScaleToFill; + self.addressLabel.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}"); + self.addressLabel.enabled = YES; + self.addressLabel.hidden = NO; + self.addressLabel.lineBreakMode = UILineBreakModeTailTruncation; + self.addressLabel.minimumFontSize = 10.000; + self.addressLabel.multipleTouchEnabled = NO; + self.addressLabel.numberOfLines = 1; + self.addressLabel.opaque = NO; + self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0); + self.addressLabel.text = @"Loading..."; + self.addressLabel.textAlignment = UITextAlignmentLeft; + self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000]; + self.addressLabel.userInteractionEnabled = NO; + + NSString* frontArrowString = @"â–º"; // create arrow from Unicode char + self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)]; + self.forwardButton.enabled = YES; + self.forwardButton.imageInsets = UIEdgeInsetsZero; + + NSString* backArrowString = @"â—„"; // create arrow from Unicode char + self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)]; + self.backButton.enabled = YES; + self.backButton.imageInsets = UIEdgeInsetsZero; + + [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]]; + + self.view.backgroundColor = [UIColor grayColor]; + [self.view addSubview:self.toolbar]; + [self.view addSubview:self.addressLabel]; + [self.view addSubview:self.spinner]; +} + +- (void)showLocationBar:(BOOL)show +{ + CGRect addressLabelFrame = self.addressLabel.frame; + BOOL locationBarVisible = (addressLabelFrame.size.height > 0); + + // prevent double show/hide + if (locationBarVisible == show) { + return; + } + + if (show) { + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= FOOTER_HEIGHT; + self.webView.frame = webViewBounds; + + CGRect addressLabelFrame = self.addressLabel.frame; + addressLabelFrame.size.height = LOCATIONBAR_HEIGHT; + self.addressLabel.frame = addressLabelFrame; + } else { + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= TOOLBAR_HEIGHT; + self.webView.frame = webViewBounds; + + CGRect addressLabelFrame = self.addressLabel.frame; + addressLabelFrame.size.height = 0; + self.addressLabel.frame = addressLabelFrame; + } +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; +} + +- (void)viewDidUnload +{ + [self.webView loadHTMLString:nil baseURL:nil]; + [super viewDidUnload]; +} + +- (void)close +{ + if ([self respondsToSelector:@selector(presentingViewController)]) { + [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil]; + } else { + [[self parentViewController] dismissModalViewControllerAnimated:YES]; + } + + if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) { + [self.navigationDelegate browserExit]; + } +} + +- (void)navigateTo:(NSURL*)url +{ + NSURLRequest* request = [NSURLRequest requestWithURL:url]; + + [self.webView loadRequest:request]; +} + +- (void)goBack:(id)sender +{ + [self.webView goBack]; +} + +- (void)goForward:(id)sender +{ + [self.webView goForward]; +} + +#pragma mark UIWebViewDelegate + +- (void)webViewDidStartLoad:(UIWebView*)theWebView +{ + // loading url, start spinner, update back/forward + + self.addressLabel.text = @"Loading..."; + self.backButton.enabled = theWebView.canGoBack; + self.forwardButton.enabled = theWebView.canGoForward; + + [self.spinner startAnimating]; + + if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStart:)]) { + [self.navigationDelegate browserLoadStart:theWebView.request.URL]; + } +} + +- (void)webViewDidFinishLoad:(UIWebView*)theWebView +{ + // update url, stop spinner, update back/forward + + self.addressLabel.text = theWebView.request.URL.absoluteString; + self.backButton.enabled = theWebView.canGoBack; + self.forwardButton.enabled = theWebView.canGoForward; + + [self.spinner stopAnimating]; + + if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStop:)]) { + [self.navigationDelegate browserLoadStop:theWebView.request.URL]; + } +} + +- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error +{ + // log fail message, stop spinner, update back/forward + NSLog(@"webView:didFailLoadWithError - %@", [error localizedDescription]); + + self.backButton.enabled = theWebView.canGoBack; + self.forwardButton.enabled = theWebView.canGoForward; + [self.spinner stopAnimating]; + + self.addressLabel.text = @"Load Error"; +} + +#pragma mark CDVScreenOrientationDelegate + +- (BOOL)shouldAutorotate +{ + if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) { + return [self.orientationDelegate shouldAutorotate]; + } + return YES; +} + +- (NSUInteger)supportedInterfaceOrientations +{ + if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) { + return [self.orientationDelegate supportedInterfaceOrientations]; + } + + return 1 << UIInterfaceOrientationPortrait; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) { + return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation]; + } + + return YES; +} + +@end + +@implementation CDVInAppBrowserOptions + +- (id)init +{ + if (self = [super init]) { + // default values + self.location = YES; + } + + return self; +} + ++ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options +{ + CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init]; + + // NOTE: this parsing does not handle quotes within values + NSArray* pairs = [options componentsSeparatedByString:@","]; + + // parse keys and values, set the properties + for (NSString* pair in pairs) { + NSArray* keyvalue = [pair componentsSeparatedByString:@"="]; + + if ([keyvalue count] == 2) { + NSString* key = [[keyvalue objectAtIndex:0] lowercaseString]; + NSString* value = [keyvalue objectAtIndex:1]; + BOOL valueBool = [[value lowercaseString] isEqualToString:@"yes"]; + + // set the property according to the key name + if ([obj respondsToSelector:NSSelectorFromString(key)]) { + [obj setValue:[NSNumber numberWithBool:valueBool] forKey:key]; + } + } + } + + return obj; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h new file mode 100755 index 0000000..6eb0099 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h @@ -0,0 +1,57 @@ +/* + 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 CDVInvokedUrlCommand : NSObject { + NSString* _callbackId; + NSString* _className; + NSString* _methodName; + NSArray* _arguments; +} + +@property (nonatomic, readonly) NSArray* arguments; +@property (nonatomic, readonly) NSString* callbackId; +@property (nonatomic, readonly) NSString* className; +@property (nonatomic, readonly) NSString* methodName; + ++ (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry; + +- (id)initWithArguments:(NSArray*)arguments + callbackId :(NSString*)callbackId + className :(NSString*)className + methodName :(NSString*)methodName; + +- (id)initFromJson:(NSArray*)jsonEntry; + +// The first NSDictionary found in the arguments will be returned in legacyDict. +// The arguments array with be prepended with the callbackId and have the first +// dict removed from it. +- (void)legacyArguments:(NSMutableArray**)legacyArguments andDict:(NSMutableDictionary**)legacyDict; + +// Returns the argument at the given index. +// If index >= the number of arguments, returns nil. +// If the argument at the given index is NSNull, returns nil. +- (id)argumentAtIndex:(NSUInteger)index; +// Same as above, but returns defaultValue instead of nil. +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue; +// Same as above, but returns defaultValue instead of nil, and if the argument is not of the expected class, returns defaultValue +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue andClass:(Class)aClass; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m new file mode 100755 index 0000000..833baad --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m @@ -0,0 +1,112 @@ +/* + 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 "CDVInvokedUrlCommand.h" +#import "JSONKit.h" + +@implementation CDVInvokedUrlCommand + +@synthesize arguments = _arguments; +@synthesize callbackId = _callbackId; +@synthesize className = _className; +@synthesize methodName = _methodName; + ++ (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry +{ + return [[CDVInvokedUrlCommand alloc] initFromJson:jsonEntry]; +} + +- (id)initFromJson:(NSArray*)jsonEntry +{ + id tmp = [jsonEntry objectAtIndex:0]; + NSString* callbackId = tmp == [NSNull null] ? nil : tmp; + NSString* className = [jsonEntry objectAtIndex:1]; + NSString* methodName = [jsonEntry objectAtIndex:2]; + NSMutableArray* arguments = [jsonEntry objectAtIndex:3]; + + return [self initWithArguments:arguments + callbackId:callbackId + className:className + methodName:methodName]; +} + +- (id)initWithArguments:(NSArray*)arguments + callbackId:(NSString*)callbackId + className:(NSString*)className + methodName:(NSString*)methodName +{ + self = [super init]; + if (self != nil) { + _arguments = arguments; + _callbackId = callbackId; + _className = className; + _methodName = methodName; + } + return self; +} + +- (void)legacyArguments:(NSMutableArray**)legacyArguments andDict:(NSMutableDictionary**)legacyDict +{ + NSMutableArray* newArguments = [NSMutableArray arrayWithArray:_arguments]; + + for (NSUInteger i = 0; i < [newArguments count]; ++i) { + if ([[newArguments objectAtIndex:i] isKindOfClass:[NSDictionary class]]) { + if (legacyDict != NULL) { + *legacyDict = [newArguments objectAtIndex:i]; + } + [newArguments removeObjectAtIndex:i]; + break; + } + } + + // Legacy (two versions back) has no callbackId. + if (_callbackId != nil) { + [newArguments insertObject:_callbackId atIndex:0]; + } + if (legacyArguments != NULL) { + *legacyArguments = newArguments; + } +} + +- (id)argumentAtIndex:(NSUInteger)index +{ + return [self argumentAtIndex:index withDefault:nil]; +} + +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue +{ + return [self argumentAtIndex:index withDefault:defaultValue andClass:nil]; +} + +- (id)argumentAtIndex:(NSUInteger)index withDefault:(id)defaultValue andClass:(Class)aClass +{ + if (index >= [_arguments count]) { + return defaultValue; + } + id ret = [_arguments objectAtIndex:index]; + if (ret == [NSNull null]) { + ret = defaultValue; + } + if ((aClass != nil) && ![ret isKindOfClass:aClass]) { + ret = defaultValue; + } + return ret; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.h b/iPhone/CordovaLib/Classes/CDVLocalStorage.h new file mode 100755 index 0000000..e5e3112 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.h @@ -0,0 +1,50 @@ +/* + 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 "CDVPlugin.h" + +#define kCDVLocalStorageErrorDomain @"kCDVLocalStorageErrorDomain" +#define kCDVLocalStorageFileOperationError 1 + +@interface CDVLocalStorage : CDVPlugin <UIWebViewDelegate> + +@property (nonatomic, readonly, strong) NSMutableArray* backupInfo; + +- (BOOL)shouldBackup; +- (BOOL)shouldRestore; +- (void)backup:(CDVInvokedUrlCommand*)command; +- (void)restore:(CDVInvokedUrlCommand*)command; + ++ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType; +// Visible for testing. ++ (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict + bundlePath :(NSString*)bundlePath + fileManager :(NSFileManager*)fileManager; +@end + +@interface CDVBackupInfo : NSObject + +@property (nonatomic, copy) NSString* original; +@property (nonatomic, copy) NSString* backup; +@property (nonatomic, copy) NSString* label; + +- (BOOL)shouldBackup; +- (BOOL)shouldRestore; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.m b/iPhone/CordovaLib/Classes/CDVLocalStorage.m new file mode 100755 index 0000000..217f611 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.m @@ -0,0 +1,521 @@ +/* + 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 "CDVLocalStorage.h" +#import "CDV.h" + +@interface CDVLocalStorage () + +@property (nonatomic, readwrite, strong) NSMutableArray* backupInfo; // array of CDVBackupInfo objects +@property (nonatomic, readwrite, weak) id <UIWebViewDelegate> webviewDelegate; + +@end + +@implementation CDVLocalStorage + +@synthesize backupInfo, webviewDelegate; + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings +{ + self = (CDVLocalStorage*)[super initWithWebView:theWebView settings:classSettings]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) + name:UIApplicationWillResignActiveNotification object:nil]; + + self.backupInfo = [[self class] createBackupInfoWithCloudBackup:[(NSString*)[classSettings objectForKey:@"backupType"] isEqualToString:@"cloud"]]; + + // over-ride current webview delegate (for restore reasons) + self.webviewDelegate = theWebView.delegate; + theWebView.delegate = self; + } + + return self; +} + +#pragma mark - +#pragma mark Plugin interface methods + ++ (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir:(NSString*)backupDir targetDirNests:(BOOL)targetDirNests backupDirNests:(BOOL)backupDirNests rename:(BOOL)rename +{ + /* + This "helper" does so much work and has so many options it would probably be clearer to refactor the whole thing. + Basically, there are three database locations: + + 1. "Normal" dir -- LIB/<nested dires WebKit/LocalStorage etc>/<normal filenames> + 2. "Caches" dir -- LIB/Caches/<normal filenames> + 3. "Backup" dir -- DOC/Backups/<renamed filenames> + + And between these three, there are various migration paths, most of which only consider 2 of the 3, which is why this helper is based on 2 locations and has a notion of "direction". + */ + NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:3]; + + NSString* original; + NSString* backup; + CDVBackupInfo* backupItem; + + // ////////// 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")]; + + backupItem = [[CDVBackupInfo alloc] init]; + backupItem.backup = backup; + backupItem.original = original; + backupItem.label = @"localStorage database"; + + [backupInfo addObject:backupItem]; + + // ////////// 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")]; + + backupItem = [[CDVBackupInfo alloc] init]; + backupItem.backup = backup; + backupItem.original = original; + backupItem.label = @"websql main database"; + + [backupInfo addObject:backupItem]; + + // ////////// 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")]; + + backupItem = [[CDVBackupInfo alloc] init]; + backupItem.backup = backup; + backupItem.original = original; + backupItem.label = @"websql databases"; + + [backupInfo addObject:backupItem]; + + return backupInfo; +} + ++ (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* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"]; + NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"]; + + // create the backups folder, if needed + [[NSFileManager defaultManager] createDirectoryAtPath:backupsFolder withIntermediateDirectories:YES attributes:nil error:nil]; + + [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:backupsFolder] skip:!cloudBackup]; + + return [self createBackupInfoWithTargetDir:cacheFolder backupDir:backupsFolder targetDirNests:NO backupDirNests:NO rename:YES]; +} + ++ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL skip:(BOOL)skip +{ + NSAssert(IsAtLeastiOSVersion(@"5.1"), @"Cannot mark files for NSURLIsExcludedFromBackupKey on iOS less than 5.1"); + + NSError* error = nil; + BOOL success = [URL setResourceValue:[NSNumber numberWithBool:skip] forKey:NSURLIsExcludedFromBackupKey error:&error]; + if (!success) { + NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); + } + return success; +} + ++ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError * __autoreleasing*)error +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + + if (![fileManager fileExistsAtPath:src]) { + NSString* errorString = [NSString stringWithFormat:@"%@ file does not exist.", src]; + if (error != NULL) { + (*error) = [NSError errorWithDomain:kCDVLocalStorageErrorDomain + code:kCDVLocalStorageFileOperationError + userInfo:[NSDictionary dictionaryWithObject:errorString + forKey:NSLocalizedDescriptionKey]]; + } + return NO; + } + + // generate unique filepath in temp directory + CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); + CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); + NSString* tempBackup = [[NSTemporaryDirectory () stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"]; + CFRelease(uuidString); + CFRelease(uuidRef); + + BOOL destExists = [fileManager fileExistsAtPath:dest]; + + // backup the dest + if (destExists && ![fileManager copyItemAtPath:dest toPath:tempBackup error:error]) { + return NO; + } + + // remove the dest + if (destExists && ![fileManager removeItemAtPath:dest error:error]) { + return NO; + } + + // create path to dest + if (!destExists && ![fileManager createDirectoryAtPath:[dest stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:error]) { + return NO; + } + + // copy src to dest + if ([fileManager copyItemAtPath:src toPath:dest error:error]) { + // success - cleanup - delete the backup to the dest + if ([fileManager fileExistsAtPath:tempBackup]) { + [fileManager removeItemAtPath:tempBackup error:error]; + } + return YES; + } else { + // failure - we restore the temp backup file to dest + [fileManager copyItemAtPath:tempBackup toPath:dest error:error]; + // cleanup - delete the backup to the dest + if ([fileManager fileExistsAtPath:tempBackup]) { + [fileManager removeItemAtPath:tempBackup error:error]; + } + return NO; + } +} + +- (BOOL)shouldBackup +{ + for (CDVBackupInfo* info in self.backupInfo) { + if ([info shouldBackup]) { + return YES; + } + } + + return NO; +} + +- (BOOL)shouldRestore +{ + for (CDVBackupInfo* info in self.backupInfo) { + if ([info shouldRestore]) { + return YES; + } + } + + return NO; +} + +/* copy from webkitDbLocation to persistentDbLocation */ +- (void)backup:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + + NSError* __autoreleasing error = nil; + CDVPluginResult* result = nil; + NSString* message = nil; + + for (CDVBackupInfo* info in self.backupInfo) { + if ([info shouldBackup]) { + [[self class] copyFrom:info.original to:info.backup error:&error]; + + if (callbackId) { + if (error == nil) { + message = [NSString stringWithFormat:@"Backed up: %@", info.label]; + NSLog(@"%@", message); + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) backup: %@", info.label, [error localizedDescription]]; + NSLog(@"%@", message); + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + } + } + } +} + +/* copy from persistentDbLocation to webkitDbLocation */ +- (void)restore:(CDVInvokedUrlCommand*)command +{ + NSError* __autoreleasing error = nil; + CDVPluginResult* result = nil; + NSString* message = nil; + + for (CDVBackupInfo* info in self.backupInfo) { + if ([info shouldRestore]) { + [[self class] copyFrom:info.backup to:info.original error:&error]; + + if (error == nil) { + message = [NSString stringWithFormat:@"Restored: %@", info.label]; + NSLog(@"%@", message); + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) restore: %@", info.label, [error localizedDescription]]; + NSLog(@"%@", message); + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + } + } +} + ++ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType +{ + [self __verifyAndFixDatabaseLocations]; + [self __restoreLegacyDatabaseLocationsWithBackupType:backupType]; +} + ++ (void)__verifyAndFixDatabaseLocations +{ + NSBundle* mainBundle = [NSBundle mainBundle]; + NSString* bundlePath = [[mainBundle bundlePath] stringByDeletingLastPathComponent]; + NSString* bundleIdentifier = [[mainBundle infoDictionary] objectForKey:@"CFBundleIdentifier"]; + NSString* appPlistPath = [bundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"Library/Preferences/%@.plist", bundleIdentifier]]; + + NSMutableDictionary* appPlistDict = [NSMutableDictionary dictionaryWithContentsOfFile:appPlistPath]; + BOOL modified = [[self class] __verifyAndFixDatabaseLocationsWithAppPlistDict:appPlistDict + bundlePath:bundlePath + fileManager:[NSFileManager defaultManager]]; + + if (modified) { + BOOL ok = [appPlistDict writeToFile:appPlistPath atomically:YES]; + [[NSUserDefaults standardUserDefaults] synchronize]; + NSLog(@"Fix applied for database locations?: %@", ok ? @"YES" : @"NO"); + } +} + ++ (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict + bundlePath:(NSString*)bundlePath + fileManager:(NSFileManager*)fileManager +{ + NSString* libraryCaches = @"Library/Caches"; + NSString* libraryWebKit = @"Library/WebKit"; + + NSArray* keysToCheck = [NSArray arrayWithObjects: + @"WebKitLocalStorageDatabasePathPreferenceKey", + @"WebDatabaseDirectory", + nil]; + + BOOL dirty = NO; + + for (NSString* key in keysToCheck) { + NSString* value = [appPlistDict objectForKey:key]; + // verify key exists, and path is in app bundle, if not - fix + if ((value != nil) && ![value hasPrefix:bundlePath]) { + // the pathSuffix to use may be wrong - OTA upgrades from < 5.1 to 5.1 do keep the old path Library/WebKit, + // while Xcode synced ones do change the storage location to Library/Caches + NSString* newBundlePath = [bundlePath stringByAppendingPathComponent:libraryCaches]; + if (![fileManager fileExistsAtPath:newBundlePath]) { + newBundlePath = [bundlePath stringByAppendingPathComponent:libraryWebKit]; + } + [appPlistDict setValue:newBundlePath forKey:key]; + dirty = YES; + } + } + + return dirty; +} + ++ (void)__restoreLegacyDatabaseLocationsWithBackupType:(NSString*)backupType +{ + // on iOS 6, if you toggle between cloud/local backup, you must move database locations. Default upgrade from iOS5.1 to iOS6 is like a toggle from local to cloud. + if (!IsAtLeastiOSVersion(@"6.0")) { + return; + } + + NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + + NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:0]; + + if ([backupType isEqualToString:@"cloud"]) { + // We would like to restore old backups/caches databases to the new destination (nested in lib folder) + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appDocumentsFolder stringByAppendingPathComponent:@"Backups"] targetDirNests:YES backupDirNests:NO rename:YES]]; + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] targetDirNests:YES backupDirNests:NO rename:NO]]; + } else { + // For ios6 local backups we also want to restore from Backups dir -- but we don't need to do that here, since the plugin will do that itself. + [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] backupDir:appLibraryFolder targetDirNests:NO backupDirNests:YES rename:NO]]; + } + + NSFileManager* manager = [NSFileManager defaultManager]; + + for (CDVBackupInfo* info in backupInfo) { + if ([manager fileExistsAtPath:info.backup]) { + if ([info shouldRestore]) { + NSLog(@"Restoring old webstorage backup. From: '%@' To: '%@'.", info.backup, info.original); + [self copyFrom:info.backup to:info.original error:nil]; + } + NSLog(@"Removing old webstorage backup: '%@'.", info.backup); + [manager removeItemAtPath:info.backup error:nil]; + } + } + + [[NSUserDefaults standardUserDefaults] setBool:[backupType isEqualToString:@"cloud"] forKey:@"WebKitStoreWebDataForBackup"]; +} + +#pragma mark - +#pragma mark Notification handlers + +- (void)onResignActive +{ + UIDevice* device = [UIDevice currentDevice]; + NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"]; + + BOOL isMultitaskingSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]; + + if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default) + exitsOnSuspend = [NSNumber numberWithBool:NO]; + } + + if (exitsOnSuspend) { + [self backup:nil]; + } else if (isMultitaskingSupported) { + __block UIBackgroundTaskIdentifier backgroundTaskID = UIBackgroundTaskInvalid; + + backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; + backgroundTaskID = UIBackgroundTaskInvalid; + NSLog (@"Background task to backup WebSQL/LocalStorage expired."); + }]; + CDVLocalStorage __weak* weakSelf = self; + [self.commandDelegate runInBackground:^{ + [weakSelf backup:nil]; + + [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; + backgroundTaskID = UIBackgroundTaskInvalid; + }]; + } +} + +- (void)onAppTerminate +{ + [self onResignActive]; +} + +#pragma mark - +#pragma mark UIWebviewDelegate implementation and forwarding + +- (void)webViewDidStartLoad:(UIWebView*)theWebView +{ + [self restore:nil]; + + return [self.webviewDelegate webViewDidStartLoad:theWebView]; +} + +- (void)webViewDidFinishLoad:(UIWebView*)theWebView +{ + return [self.webviewDelegate webViewDidFinishLoad:theWebView]; +} + +- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error +{ + return [self.webviewDelegate webView:theWebView didFailLoadWithError:error]; +} + +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + return [self.webviewDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; +} + +#pragma mark - +#pragma mark Over-rides + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; // this will remove all notification unless added using addObserverForName:object:queue:usingBlock: +} + +@end + +#pragma mark - +#pragma mark CDVBackupInfo implementation + +@implementation CDVBackupInfo + +@synthesize original, backup, label; + +- (BOOL)file:(NSString*)aPath isNewerThanFile:(NSString*)bPath +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* __autoreleasing error = nil; + + NSDictionary* aPathAttribs = [fileManager attributesOfItemAtPath:aPath error:&error]; + NSDictionary* bPathAttribs = [fileManager attributesOfItemAtPath:bPath error:&error]; + + NSDate* aPathModDate = [aPathAttribs objectForKey:NSFileModificationDate]; + NSDate* bPathModDate = [bPathAttribs objectForKey:NSFileModificationDate]; + + if ((nil == aPathModDate) && (nil == bPathModDate)) { + return NO; + } + + return [aPathModDate compare:bPathModDate] == NSOrderedDescending || bPathModDate == nil; +} + +- (BOOL)item:(NSString*)aPath isNewerThanItem:(NSString*)bPath +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + + BOOL aPathIsDir = NO, bPathIsDir = NO; + BOOL aPathExists = [fileManager fileExistsAtPath:aPath isDirectory:&aPathIsDir]; + + [fileManager fileExistsAtPath:bPath isDirectory:&bPathIsDir]; + + if (!aPathExists) { + return NO; + } + + if (!(aPathIsDir && bPathIsDir)) { // just a file + return [self file:aPath isNewerThanFile:bPath]; + } + + // essentially we want rsync here, but have to settle for our poor man's implementation + // we get the files in aPath, and see if it is newer than the file in bPath + // (it is newer if it doesn't exist in bPath) if we encounter the FIRST file that is newer, + // we return YES + NSDirectoryEnumerator* directoryEnumerator = [fileManager enumeratorAtPath:aPath]; + NSString* path; + + while ((path = [directoryEnumerator nextObject])) { + NSString* aPathFile = [aPath stringByAppendingPathComponent:path]; + NSString* bPathFile = [bPath stringByAppendingPathComponent:path]; + + BOOL isNewer = [self file:aPathFile isNewerThanFile:bPathFile]; + if (isNewer) { + return YES; + } + } + + return NO; +} + +- (BOOL)shouldBackup +{ + return [self item:self.original isNewerThanItem:self.backup]; +} + +- (BOOL)shouldRestore +{ + return [self item:self.backup isNewerThanItem:self.original]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocation.h b/iPhone/CordovaLib/Classes/CDVLocation.h new file mode 100755 index 0000000..7087d43 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLocation.h @@ -0,0 +1,104 @@ +/* + 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 <UIKit/UIKit.h> +#import <CoreLocation/CoreLocation.h> +#import "CDVPlugin.h" + +enum CDVHeadingStatus { + HEADINGSTOPPED = 0, + HEADINGSTARTING, + HEADINGRUNNING, + HEADINGERROR +}; +typedef NSUInteger CDVHeadingStatus; + +enum CDVLocationStatus { + PERMISSIONDENIED = 1, + POSITIONUNAVAILABLE, + TIMEOUT +}; +typedef NSUInteger CDVLocationStatus; + +// simple object to keep track of heading information +@interface CDVHeadingData : NSObject {} + +@property (nonatomic, assign) CDVHeadingStatus headingStatus; +@property (nonatomic, strong) CLHeading* headingInfo; +@property (nonatomic, strong) NSMutableArray* headingCallbacks; +@property (nonatomic, copy) NSString* headingFilter; +@property (nonatomic, strong) NSDate* headingTimestamp; +@property (assign) NSInteger timeout; + +@end + +// simple object to keep track of location information +@interface CDVLocationData : NSObject { + CDVLocationStatus locationStatus; + NSMutableArray* locationCallbacks; + NSMutableDictionary* watchCallbacks; + CLLocation* locationInfo; +} + +@property (nonatomic, assign) CDVLocationStatus locationStatus; +@property (nonatomic, strong) CLLocation* locationInfo; +@property (nonatomic, strong) NSMutableArray* locationCallbacks; +@property (nonatomic, strong) NSMutableDictionary* watchCallbacks; + +@end + +@interface CDVLocation : CDVPlugin <CLLocationManagerDelegate>{ + @private BOOL __locationStarted; + @private BOOL __highAccuracyEnabled; + CDVHeadingData* headingData; + CDVLocationData* locationData; +} + +@property (nonatomic, strong) CLLocationManager* locationManager; +@property (strong) CDVHeadingData* headingData; +@property (nonatomic, strong) CDVLocationData* locationData; + +- (BOOL)hasHeadingSupport; +- (void)getLocation:(CDVInvokedUrlCommand*)command; +- (void)addWatch:(CDVInvokedUrlCommand*)command; +- (void)clearWatch:(CDVInvokedUrlCommand*)command; +- (void)returnLocationInfo:(NSString*)callbackId andKeepCallback:(BOOL)keepCallback; +- (void)returnLocationError:(NSUInteger)errorCode withMessage:(NSString*)message; +- (void)startLocation:(BOOL)enableHighAccuracy; + +- (void)locationManager:(CLLocationManager*)manager + didUpdateToLocation :(CLLocation*)newLocation + fromLocation :(CLLocation*)oldLocation; + +- (void)locationManager:(CLLocationManager*)manager + didFailWithError :(NSError*)error; + +- (BOOL)isLocationServicesEnabled; + +- (void)getHeading:(CDVInvokedUrlCommand*)command; +- (void)returnHeadingInfo:(NSString*)callbackId keepCallback:(BOOL)bRetain; +- (void)watchHeadingFilter:(CDVInvokedUrlCommand*)command; +- (void)stopHeading:(CDVInvokedUrlCommand*)command; +- (void)startHeadingWithFilter:(CLLocationDegrees)filter; +- (void)locationManager:(CLLocationManager*)manager + didUpdateHeading :(CLHeading*)heading; + +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager*)manager; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocation.m b/iPhone/CordovaLib/Classes/CDVLocation.m new file mode 100755 index 0000000..9814f35 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLocation.m @@ -0,0 +1,633 @@ +/* + 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 "CDVLocation.h" +#import "CDVViewController.h" +#import "NSArray+Comparisons.h" + +#pragma mark Constants + +#define kPGLocationErrorDomain @"kPGLocationErrorDomain" +#define kPGLocationDesiredAccuracyKey @"desiredAccuracy" +#define kPGLocationForcePromptKey @"forcePrompt" +#define kPGLocationDistanceFilterKey @"distanceFilter" +#define kPGLocationFrequencyKey @"frequency" + +#pragma mark - +#pragma mark Categories + +@interface NSError (JSONMethods) + +- (NSString*)JSONRepresentation; + +@end + +@interface CLLocation (JSONMethods) + +- (NSString*)JSONRepresentation; + +@end + +@interface CLHeading (JSONMethods) + +- (NSString*)JSONRepresentation; + +@end + +#pragma mark - +#pragma mark CDVHeadingData + +@implementation CDVHeadingData + +@synthesize headingStatus, headingInfo, headingCallbacks, headingFilter, headingTimestamp, timeout; +- (CDVHeadingData*)init +{ + self = (CDVHeadingData*)[super init]; + if (self) { + self.headingStatus = HEADINGSTOPPED; + self.headingInfo = nil; + self.headingCallbacks = nil; + self.headingFilter = nil; + self.headingTimestamp = nil; + self.timeout = 10; + } + return self; +} + +@end + +@implementation CDVLocationData + +@synthesize locationStatus, locationInfo, locationCallbacks, watchCallbacks; +- (CDVLocationData*)init +{ + self = (CDVLocationData*)[super init]; + if (self) { + self.locationInfo = nil; + self.locationCallbacks = nil; + self.watchCallbacks = nil; + } + return self; +} + +@end + +#pragma mark - +#pragma mark CDVLocation + +@implementation CDVLocation + +@synthesize locationManager, headingData, locationData; + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVLocation*)[super initWithWebView:(UIWebView*)theWebView]; + if (self) { + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; // Tells the location manager to send updates to this object + __locationStarted = NO; + __highAccuracyEnabled = NO; + self.headingData = nil; + self.locationData = nil; + } + return self; +} + +- (BOOL)hasHeadingSupport +{ + BOOL headingInstancePropertyAvailable = [self.locationManager respondsToSelector:@selector(headingAvailable)]; // iOS 3.x + BOOL headingClassPropertyAvailable = [CLLocationManager respondsToSelector:@selector(headingAvailable)]; // iOS 4.x + + if (headingInstancePropertyAvailable) { // iOS 3.x + return [(id)self.locationManager headingAvailable]; + } else if (headingClassPropertyAvailable) { // iOS 4.x + return [CLLocationManager headingAvailable]; + } else { // iOS 2.x + return NO; + } +} + +- (BOOL)isAuthorized +{ + BOOL authorizationStatusClassPropertyAvailable = [CLLocationManager respondsToSelector:@selector(authorizationStatus)]; // iOS 4.2+ + + if (authorizationStatusClassPropertyAvailable) { + NSUInteger authStatus = [CLLocationManager authorizationStatus]; + return (authStatus == kCLAuthorizationStatusAuthorized) || (authStatus == kCLAuthorizationStatusNotDetermined); + } + + // by default, assume YES (for iOS < 4.2) + return YES; +} + +- (BOOL)isLocationServicesEnabled +{ + BOOL locationServicesEnabledInstancePropertyAvailable = [self.locationManager respondsToSelector:@selector(locationServicesEnabled)]; // iOS 3.x + BOOL locationServicesEnabledClassPropertyAvailable = [CLLocationManager respondsToSelector:@selector(locationServicesEnabled)]; // iOS 4.x + + if (locationServicesEnabledClassPropertyAvailable) { // iOS 4.x + return [CLLocationManager locationServicesEnabled]; + } else if (locationServicesEnabledInstancePropertyAvailable) { // iOS 2.x, iOS 3.x + return [(id)self.locationManager locationServicesEnabled]; + } else { + return NO; + } +} + +- (void)startLocation:(BOOL)enableHighAccuracy +{ + if (![self isLocationServicesEnabled]) { + [self returnLocationError:PERMISSIONDENIED withMessage:@"Location services are not enabled."]; + return; + } + if (![self isAuthorized]) { + NSString* message = nil; + BOOL authStatusAvailable = [CLLocationManager respondsToSelector:@selector(authorizationStatus)]; // iOS 4.2+ + if (authStatusAvailable) { + NSUInteger code = [CLLocationManager authorizationStatus]; + if (code == kCLAuthorizationStatusNotDetermined) { + // could return POSITION_UNAVAILABLE but need to coordinate with other platforms + message = @"User undecided on application's use of location services."; + } else if (code == kCLAuthorizationStatusRestricted) { + message = @"Application's use of location services is restricted."; + } + } + // PERMISSIONDENIED is only PositionError that makes sense when authorization denied + [self returnLocationError:PERMISSIONDENIED withMessage:message]; + + return; + } + + // Tell the location manager to start notifying us of location updates. We + // first stop, and then start the updating to ensure we get at least one + // update, even if our location did not change. + [self.locationManager stopUpdatingLocation]; + [self.locationManager startUpdatingLocation]; + __locationStarted = YES; + if (enableHighAccuracy) { + __highAccuracyEnabled = YES; + // Set to distance filter to "none" - which should be the minimum for best results. + self.locationManager.distanceFilter = kCLDistanceFilterNone; + // Set desired accuracy to Best. + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; + } else { + __highAccuracyEnabled = NO; + // TODO: Set distance filter to 10 meters? and desired accuracy to nearest ten meters? arbitrary. + self.locationManager.distanceFilter = 10; + self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; + } +} + +- (void)_stopLocation +{ + if (__locationStarted) { + if (![self isLocationServicesEnabled]) { + return; + } + + [self.locationManager stopUpdatingLocation]; + __locationStarted = NO; + __highAccuracyEnabled = NO; + } +} + +- (void)locationManager:(CLLocationManager*)manager + didUpdateToLocation:(CLLocation*)newLocation + fromLocation:(CLLocation*)oldLocation +{ + CDVLocationData* cData = self.locationData; + + cData.locationInfo = newLocation; + if (self.locationData.locationCallbacks.count > 0) { + for (NSString* callbackId in self.locationData.locationCallbacks) { + [self returnLocationInfo:callbackId andKeepCallback:NO]; + } + + [self.locationData.locationCallbacks removeAllObjects]; + } + if (self.locationData.watchCallbacks.count > 0) { + for (NSString* timerId in self.locationData.watchCallbacks) { + [self returnLocationInfo:[self.locationData.watchCallbacks objectForKey:timerId] andKeepCallback:YES]; + } + } else { + // No callbacks waiting on us anymore, turn off listening. + [self _stopLocation]; + } +} + +- (void)getLocation:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + BOOL enableHighAccuracy = [[command.arguments objectAtIndex:0] boolValue]; + + if ([self isLocationServicesEnabled] == NO) { + NSMutableDictionary* posError = [NSMutableDictionary dictionaryWithCapacity:2]; + [posError setObject:[NSNumber numberWithInt:PERMISSIONDENIED] forKey:@"code"]; + [posError setObject:@"Location services are disabled." forKey:@"message"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:posError]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + if (!self.locationData) { + self.locationData = [[CDVLocationData alloc] init]; + } + CDVLocationData* lData = self.locationData; + if (!lData.locationCallbacks) { + lData.locationCallbacks = [NSMutableArray arrayWithCapacity:1]; + } + + if (!__locationStarted || (__highAccuracyEnabled != enableHighAccuracy)) { + // add the callbackId into the array so we can call back when get data + if (callbackId != nil) { + [lData.locationCallbacks addObject:callbackId]; + } + // Tell the location manager to start notifying us of heading updates + [self startLocation:enableHighAccuracy]; + } else { + [self returnLocationInfo:callbackId andKeepCallback:NO]; + } + } +} + +- (void)addWatch:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* timerId = [command.arguments objectAtIndex:0]; + BOOL enableHighAccuracy = [[command.arguments objectAtIndex:1] boolValue]; + + if (!self.locationData) { + self.locationData = [[CDVLocationData alloc] init]; + } + CDVLocationData* lData = self.locationData; + + if (!lData.watchCallbacks) { + lData.watchCallbacks = [NSMutableDictionary dictionaryWithCapacity:1]; + } + + // add the callbackId into the dictionary so we can call back whenever get data + [lData.watchCallbacks setObject:callbackId forKey:timerId]; + + if ([self isLocationServicesEnabled] == NO) { + NSMutableDictionary* posError = [NSMutableDictionary dictionaryWithCapacity:2]; + [posError setObject:[NSNumber numberWithInt:PERMISSIONDENIED] forKey:@"code"]; + [posError setObject:@"Location services are disabled." forKey:@"message"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:posError]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + if (!__locationStarted || (__highAccuracyEnabled != enableHighAccuracy)) { + // Tell the location manager to start notifying us of location updates + [self startLocation:enableHighAccuracy]; + } + } +} + +- (void)clearWatch:(CDVInvokedUrlCommand*)command +{ + NSString* timerId = [command.arguments objectAtIndex:0]; + + if (self.locationData && self.locationData.watchCallbacks && [self.locationData.watchCallbacks objectForKey:timerId]) { + [self.locationData.watchCallbacks removeObjectForKey:timerId]; + } +} + +- (void)stopLocation:(CDVInvokedUrlCommand*)command +{ + [self _stopLocation]; +} + +- (void)returnLocationInfo:(NSString*)callbackId andKeepCallback:(BOOL)keepCallback +{ + CDVPluginResult* result = nil; + CDVLocationData* lData = self.locationData; + + if (lData && !lData.locationInfo) { + // return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:POSITIONUNAVAILABLE]; + } else if (lData && lData.locationInfo) { + CLLocation* lInfo = lData.locationInfo; + NSMutableDictionary* returnInfo = [NSMutableDictionary dictionaryWithCapacity:8]; + NSNumber* timestamp = [NSNumber numberWithDouble:([lInfo.timestamp timeIntervalSince1970] * 1000)]; + [returnInfo setObject:timestamp forKey:@"timestamp"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.speed] forKey:@"velocity"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.verticalAccuracy] forKey:@"altitudeAccuracy"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.horizontalAccuracy] forKey:@"accuracy"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.course] forKey:@"heading"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.altitude] forKey:@"altitude"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.coordinate.latitude] forKey:@"latitude"]; + [returnInfo setObject:[NSNumber numberWithDouble:lInfo.coordinate.longitude] forKey:@"longitude"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo]; + [result setKeepCallbackAsBool:keepCallback]; + } + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +- (void)returnLocationError:(NSUInteger)errorCode withMessage:(NSString*)message +{ + NSMutableDictionary* posError = [NSMutableDictionary dictionaryWithCapacity:2]; + + [posError setObject:[NSNumber numberWithInt:errorCode] forKey:@"code"]; + [posError setObject:message ? message:@"" forKey:@"message"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:posError]; + + for (NSString* callbackId in self.locationData.locationCallbacks) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + + [self.locationData.locationCallbacks removeAllObjects]; + + for (NSString* callbackId in self.locationData.watchCallbacks) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +// called to get the current heading +// Will call location manager to startUpdatingHeading if necessary + +- (void)getHeading:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:nil]; + NSNumber* filter = [options valueForKey:@"filter"]; + + if (filter) { + [self watchHeadingFilter:command]; + return; + } + if ([self hasHeadingSupport] == NO) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:20]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + // heading retrieval does is not affected by disabling locationServices and authorization of app for location services + if (!self.headingData) { + self.headingData = [[CDVHeadingData alloc] init]; + } + CDVHeadingData* hData = self.headingData; + + if (!hData.headingCallbacks) { + hData.headingCallbacks = [NSMutableArray arrayWithCapacity:1]; + } + // add the callbackId into the array so we can call back when get data + [hData.headingCallbacks addObject:callbackId]; + + if ((hData.headingStatus != HEADINGRUNNING) && (hData.headingStatus != HEADINGERROR)) { + // Tell the location manager to start notifying us of heading updates + [self startHeadingWithFilter:0.2]; + } else { + [self returnHeadingInfo:callbackId keepCallback:NO]; + } + } +} + +// called to request heading updates when heading changes by a certain amount (filter) +- (void)watchHeadingFilter:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:nil]; + NSNumber* filter = [options valueForKey:@"filter"]; + CDVHeadingData* hData = self.headingData; + + if ([self hasHeadingSupport] == NO) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:20]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } else { + if (!hData) { + self.headingData = [[CDVHeadingData alloc] init]; + hData = self.headingData; + } + if (hData.headingStatus != HEADINGRUNNING) { + // Tell the location manager to start notifying us of heading updates + [self startHeadingWithFilter:[filter doubleValue]]; + } else { + // if already running check to see if due to existing watch filter + if (hData.headingFilter && ![hData.headingFilter isEqualToString:callbackId]) { + // new watch filter being specified + // send heading data one last time to clear old successCallback + [self returnHeadingInfo:hData.headingFilter keepCallback:NO]; + } + } + // save the new filter callback and update the headingFilter setting + hData.headingFilter = callbackId; + // check if need to stop and restart in order to change value??? + self.locationManager.headingFilter = [filter doubleValue]; + } +} + +- (void)returnHeadingInfo:(NSString*)callbackId keepCallback:(BOOL)bRetain +{ + CDVPluginResult* result = nil; + CDVHeadingData* hData = self.headingData; + + self.headingData.headingTimestamp = [NSDate date]; + + if (hData && (hData.headingStatus == HEADINGERROR)) { + // return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:0]; + } else if (hData && (hData.headingStatus == HEADINGRUNNING) && hData.headingInfo) { + // if there is heading info, return it + CLHeading* hInfo = hData.headingInfo; + NSMutableDictionary* returnInfo = [NSMutableDictionary dictionaryWithCapacity:4]; + NSNumber* timestamp = [NSNumber numberWithDouble:([hInfo.timestamp timeIntervalSince1970] * 1000)]; + [returnInfo setObject:timestamp forKey:@"timestamp"]; + [returnInfo setObject:[NSNumber numberWithDouble:hInfo.magneticHeading] forKey:@"magneticHeading"]; + id trueHeading = __locationStarted ? (id)[NSNumber numberWithDouble : hInfo.trueHeading] : (id)[NSNull null]; + [returnInfo setObject:trueHeading forKey:@"trueHeading"]; + [returnInfo setObject:[NSNumber numberWithDouble:hInfo.headingAccuracy] forKey:@"headingAccuracy"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo]; + [result setKeepCallbackAsBool:bRetain]; + } + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } +} + +- (void)stopHeading:(CDVInvokedUrlCommand*)command +{ + // CDVHeadingData* hData = self.headingData; + if (self.headingData && (self.headingData.headingStatus != HEADINGSTOPPED)) { + if (self.headingData.headingFilter) { + // callback one last time to clear callback + [self returnHeadingInfo:self.headingData.headingFilter keepCallback:NO]; + self.headingData.headingFilter = nil; + } + [self.locationManager stopUpdatingHeading]; + NSLog(@"heading STOPPED"); + self.headingData = nil; + } +} + +// helper method to check the orientation and start updating headings +- (void)startHeadingWithFilter:(CLLocationDegrees)filter +{ + if ([self.locationManager respondsToSelector:@selector(headingOrientation)]) { + UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation]; + if (currentOrientation != UIDeviceOrientationUnknown) { + CDVViewController* cdvViewController = (CDVViewController*)self.viewController; + + if ([cdvViewController supportsOrientation:currentOrientation]) { + self.locationManager.headingOrientation = (CLDeviceOrientation)currentOrientation; + // FYI UIDeviceOrientation and CLDeviceOrientation enums are currently the same + } + } + } + self.locationManager.headingFilter = filter; + [self.locationManager startUpdatingHeading]; + self.headingData.headingStatus = HEADINGSTARTING; +} + +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager*)manager +{ + return YES; +} + +- (void)locationManager:(CLLocationManager*)manager + didUpdateHeading:(CLHeading*)heading +{ + CDVHeadingData* hData = self.headingData; + + // normally we would clear the delegate to stop getting these notifications, but + // we are sharing a CLLocationManager to get location data as well, so we do a nil check here + // ideally heading and location should use their own CLLocationManager instances + if (hData == nil) { + return; + } + + // save the data for next call into getHeadingData + hData.headingInfo = heading; + BOOL bTimeout = NO; + if (!hData.headingFilter && hData.headingTimestamp) { + bTimeout = fabs([hData.headingTimestamp timeIntervalSinceNow]) > hData.timeout; + } + + if (hData.headingStatus == HEADINGSTARTING) { + hData.headingStatus = HEADINGRUNNING; // so returnHeading info will work + + // this is the first update + for (NSString* callbackId in hData.headingCallbacks) { + [self returnHeadingInfo:callbackId keepCallback:NO]; + } + + [hData.headingCallbacks removeAllObjects]; + } + if (hData.headingFilter) { + [self returnHeadingInfo:hData.headingFilter keepCallback:YES]; + } else if (bTimeout) { + [self stopHeading:nil]; + } + hData.headingStatus = HEADINGRUNNING; // to clear any error +} + +- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error +{ + NSLog(@"locationManager::didFailWithError %@", [error localizedFailureReason]); + + // Compass Error + if ([error code] == kCLErrorHeadingFailure) { + CDVHeadingData* hData = self.headingData; + if (hData) { + if (hData.headingStatus == HEADINGSTARTING) { + // heading error during startup - report error + for (NSString* callbackId in hData.headingCallbacks) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:0]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + + [hData.headingCallbacks removeAllObjects]; + } // else for frequency watches next call to getCurrentHeading will report error + if (hData.headingFilter) { + CDVPluginResult* resultFilter = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:0]; + [self.commandDelegate sendPluginResult:resultFilter callbackId:hData.headingFilter]; + } + hData.headingStatus = HEADINGERROR; + } + } + // Location Error + else { + CDVLocationData* lData = self.locationData; + if (lData && __locationStarted) { + // TODO: probably have to once over the various error codes and return one of: + // PositionError.PERMISSION_DENIED = 1; + // PositionError.POSITION_UNAVAILABLE = 2; + // PositionError.TIMEOUT = 3; + NSUInteger positionError = POSITIONUNAVAILABLE; + if (error.code == kCLErrorDenied) { + positionError = PERMISSIONDENIED; + } + [self returnLocationError:positionError withMessage:[error localizedDescription]]; + } + } + + [self.locationManager stopUpdatingLocation]; + __locationStarted = NO; +} + +- (void)dealloc +{ + self.locationManager.delegate = nil; +} + +- (void)onReset +{ + [self _stopLocation]; + [self.locationManager stopUpdatingHeading]; + self.headingData = nil; +} + +@end + +#pragma mark - +#pragma mark CLLocation(JSONMethods) + +@implementation CLLocation (JSONMethods) + +- (NSString*)JSONRepresentation +{ + return [NSString stringWithFormat: + @"{ 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 + ]; +} + +@end + +#pragma mark NSError(JSONMethods) + +@implementation NSError (JSONMethods) + +- (NSString*)JSONRepresentation +{ + return [NSString stringWithFormat: + @"{ code: %d, message: '%@'}", + self.code, + [self localizedDescription] + ]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLogger.h b/iPhone/CordovaLib/Classes/CDVLogger.h new file mode 100755 index 0000000..eeba63c --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLogger.h @@ -0,0 +1,26 @@ +/* + 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 "CDVPlugin.h" + +@interface CDVLogger : CDVPlugin + +- (void)logLevel:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLogger.m b/iPhone/CordovaLib/Classes/CDVLogger.m new file mode 100755 index 0000000..a37cf8a --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVLogger.m @@ -0,0 +1,38 @@ +/* + 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 "CDVLogger.h" +#import "CDV.h" + +@implementation CDVLogger + +/* log a message */ +- (void)logLevel:(CDVInvokedUrlCommand*)command +{ + id level = [command.arguments objectAtIndex:0]; + id message = [command.arguments objectAtIndex:1]; + + if ([level isEqualToString:@"LOG"]) { + NSLog(@"%@", message); + } else { + NSLog(@"%@: %@", level, message); + } +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVNotification.h b/iPhone/CordovaLib/Classes/CDVNotification.h new file mode 100755 index 0000000..1eedb54 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVNotification.h @@ -0,0 +1,36 @@ +/* + 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> +#import <UIKit/UIKit.h> +#import <AudioToolbox/AudioServices.h> +#import "CDVPlugin.h" + +@interface CDVNotification : CDVPlugin <UIAlertViewDelegate>{} + +- (void)alert:(CDVInvokedUrlCommand*)command; +- (void)confirm:(CDVInvokedUrlCommand*)command; +- (void)vibrate:(CDVInvokedUrlCommand*)command; + +@end + +@interface CDVAlertView : UIAlertView {} +@property (nonatomic, copy) NSString* callbackId; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVNotification.m b/iPhone/CordovaLib/Classes/CDVNotification.m new file mode 100755 index 0000000..992239e --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVNotification.m @@ -0,0 +1,109 @@ +/* + 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 "CDVNotification.h" +#import "NSDictionary+Extensions.h" + +@implementation CDVNotification + +- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSString*)buttons callbackId:(NSString*)callbackId +{ + CDVAlertView* alertView = [[CDVAlertView alloc] + initWithTitle:title + message:message + delegate:self + cancelButtonTitle:nil + otherButtonTitles:nil]; + + alertView.callbackId = callbackId; + + NSArray* labels = [buttons componentsSeparatedByString:@","]; + int count = [labels count]; + + for (int n = 0; n < count; n++) { + [alertView addButtonWithTitle:[labels objectAtIndex:n]]; + } + + [alertView show]; +} + +- (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"); + } + + [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; +} + +- (void)confirm:(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(@"Confirm", @"Confirm"); + } + if (!buttons) { + buttons = NSLocalizedString(@"OK,Cancel", @"OK,Cancel"); + } + + [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; +} + +/** + Callback invoked when an alert dialog's buttons are clicked. + Passes the index + label back to JS + */ +- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + CDVAlertView* cdvAlertView = (CDVAlertView*)alertView; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:++buttonIndex]; + + [self.commandDelegate sendPluginResult:result callbackId:cdvAlertView.callbackId]; +} + +- (void)vibrate:(CDVInvokedUrlCommand*)command +{ + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); +} + +@end + +@implementation CDVAlertView + +@synthesize callbackId; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.h b/iPhone/CordovaLib/Classes/CDVPlugin.h new file mode 100755 index 0000000..8c59a2b --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVPlugin.h @@ -0,0 +1,64 @@ +/* + 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> +#import <UIKit/UIKit.h> +#import "CDVPluginResult.h" +#import "NSMutableArray+QueueAdditions.h" +#import "CDVCommandDelegate.h" + +#define CDVPluginHandleOpenURLNotification @"CDVPluginHandleOpenURLNotification" +#define CDVPluginResetNotification @"CDVPluginResetNotification" +#define CDVLocalNotification @"CDVLocalNotification" + +@interface CDVPlugin : NSObject {} + +@property (nonatomic, weak) UIWebView* webView; +@property (nonatomic, strong) NSDictionary* settings; +@property (nonatomic, weak) UIViewController* viewController; +@property (nonatomic, weak) id <CDVCommandDelegate> commandDelegate; + +@property (readonly, assign) BOOL hasPendingOperation; + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings; +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView; + +- (void)handleOpenURL:(NSNotification*)notification; +- (void)onAppTerminate; +- (void)onMemoryWarning; +- (void)onReset; +- (void)dispose; + +/* + // see initWithWebView implementation + - (void) onPause {} + - (void) onResume {} + - (void) onOrientationWillChange {} + - (void) onOrientationDidChange {} + - (void)didReceiveLocalNotification:(NSNotification *)notification; + */ + +- (id)appDelegate; + +// TODO(agrieve): Deprecate these in favour of using CDVCommandDelegate directly. +- (NSString*)writeJavascript:(NSString*)javascript; +- (NSString*)success:(CDVPluginResult*)pluginResult callbackId:(NSString*)callbackId; +- (NSString*)error:(CDVPluginResult*)pluginResult callbackId:(NSString*)callbackId; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.m b/iPhone/CordovaLib/Classes/CDVPlugin.m new file mode 100755 index 0000000..53023c7 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVPlugin.m @@ -0,0 +1,148 @@ +/* + 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 "CDVPlugin.h" + +@interface CDVPlugin () + +@property (readwrite, assign) BOOL hasPendingOperation; + +@end + +@implementation CDVPlugin +@synthesize webView, settings, viewController, commandDelegate, hasPendingOperation; + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings +{ + self = [self initWithWebView:theWebView]; + if (self) { + self.settings = classSettings; + self.hasPendingOperation = NO; + } + return self; +} + +- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView +{ + self = [super init]; + if (self) { + [[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]; + + self.webView = theWebView; + + // You can listen to more app notifications, see: + // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 + + /* + // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; + + // Added in 2.3.0+ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveLocalNotification:) name:CDVLocalNotification object:nil]; + + */ + } + return self; +} + +- (void)dispose +{ + viewController = nil; + commandDelegate = nil; + webView = nil; +} + +/* +// NOTE: for onPause and onResume, calls into JavaScript must not call or trigger any blocking UI, like alerts +- (void) onPause {} +- (void) onResume {} +- (void) onOrientationWillChange {} +- (void) onOrientationDidChange {} +*/ + +/* NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts */ +- (void)handleOpenURL:(NSNotification*)notification +{ + // override to handle urls sent to your app + // register your url schemes in your App-Info.plist + + NSURL* url = [notification object]; + + if ([url isKindOfClass:[NSURL class]]) { + /* Do your thing! */ + } +} + +/* NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts */ +- (void)onAppTerminate +{ + // override this if you need to do any cleanup on app exit +} + +- (void)onMemoryWarning +{ + // override to remove caches, etc +} + +- (void)onReset +{ + // Override to cancel any long-running requests when the WebView navigates or refreshes. +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; // this will remove all notification unless added using addObserverForName:object:queue:usingBlock: +} + +- (id)appDelegate +{ + return [[UIApplication sharedApplication] delegate]; +} + +- (NSString*)writeJavascript:(NSString*)javascript +{ + return [self.webView stringByEvaluatingJavaScriptFromString:javascript]; +} + +- (NSString*)success:(CDVPluginResult*)pluginResult callbackId:(NSString*)callbackId +{ + [self.commandDelegate evalJs:[pluginResult toSuccessCallbackString:callbackId]]; + return @""; +} + +- (NSString*)error:(CDVPluginResult*)pluginResult callbackId:(NSString*)callbackId +{ + [self.commandDelegate evalJs:[pluginResult toErrorCallbackString:callbackId]]; + return @""; +} + +// default implementation does nothing, ideally, we are not registered for notification if we aren't going to do anything. +//- (void)didReceiveLocalNotification:(NSNotification *)notification +//{ +// // UILocalNotification* localNotification = [notification object]; // get the payload as a LocalNotification +//} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.h b/iPhone/CordovaLib/Classes/CDVPluginResult.h new file mode 100755 index 0000000..6dd7d45 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.h @@ -0,0 +1,59 @@ +/* + 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> + +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 CDVPluginResult : NSObject {} + +@property (nonatomic, strong, readonly) NSNumber* status; +@property (nonatomic, strong, readonly) id message; +@property (nonatomic, strong) NSNumber* keepCallback; + +- (CDVPluginResult*)init; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsString:(NSString*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArray:(NSArray*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsInt:(int)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDouble:(double)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode; + ++ (void)setVerbose:(BOOL)verbose; ++ (BOOL)isVerbose; + +- (void)setKeepCallbackAsBool:(BOOL)bKeepCallback; + +- (NSString*)toJSONString; +- (NSString*)toSuccessCallbackString:(NSString*)callbackId; +- (NSString*)toErrorCallbackString:(NSString*)callbackId; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.m b/iPhone/CordovaLib/Classes/CDVPluginResult.m new file mode 100755 index 0000000..5343dce --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.m @@ -0,0 +1,153 @@ +/* + 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 "CDVPluginResult.h" +#import "JSONKit.h" +#import "CDVDebug.h" + +@interface CDVPluginResult () + +- (CDVPluginResult*)initWithStatus:(CDVCommandStatus)statusOrdinal message:(id)theMessage; + +@end + +@implementation CDVPluginResult +@synthesize status, message, keepCallback; + +static NSArray* org_apache_cordova_CommandStatusMsgs; + ++ (void)initialize +{ + org_apache_cordova_CommandStatusMsgs = [[NSArray alloc] initWithObjects:@"No result", + @"OK", + @"Class not found", + @"Illegal access", + @"Instantiation error", + @"Malformed url", + @"IO error", + @"Invalid action", + @"JSON error", + @"Error", + nil]; +} + +- (CDVPluginResult*)init +{ + return [self initWithStatus:CDVCommandStatus_NO_RESULT message:nil]; +} + +- (CDVPluginResult*)initWithStatus:(CDVCommandStatus)statusOrdinal message:(id)theMessage +{ + self = [super init]; + if (self) { + status = [NSNumber numberWithInt:statusOrdinal]; + message = theMessage; + keepCallback = [NSNumber numberWithBool:NO]; + } + return self; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal +{ + return [[self alloc] initWithStatus:statusOrdinal message:[org_apache_cordova_CommandStatusMsgs objectAtIndex:statusOrdinal]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsString:(NSString*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArray:(NSArray*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsInt:(int)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithInt:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDouble:(double)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithDouble:theMessage]]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; +} + ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode +{ + NSDictionary* errDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"code"]; + + return [[self alloc] initWithStatus:statusOrdinal message:errDict]; +} + +- (void)setKeepCallbackAsBool:(BOOL)bKeepCallback +{ + [self setKeepCallback:[NSNumber numberWithBool:bKeepCallback]]; +} + +- (NSString*)toJSONString +{ + NSString* resultString = [[NSDictionary dictionaryWithObjectsAndKeys: + self.status, @"status", + self.message ? self. message:[NSNull null], @"message", + self.keepCallback, @"keepCallback", + nil] cdvjk_JSONString]; + + if ([[self class] isVerbose]) { + NSLog(@"PluginResult:toJSONString - %@", resultString); + } + return resultString; +} + +- (NSString*)toSuccessCallbackString:(NSString*)callbackId +{ + NSString* successCB = [NSString stringWithFormat:@"cordova.callbackSuccess('%@',%@);", callbackId, [self toJSONString]]; + + if ([[self class] isVerbose]) { + NSLog(@"PluginResult toSuccessCallbackString: %@", successCB); + } + return successCB; +} + +- (NSString*)toErrorCallbackString:(NSString*)callbackId +{ + NSString* errorCB = [NSString stringWithFormat:@"cordova.callbackError('%@',%@);", callbackId, [self toJSONString]]; + + if ([[self class] isVerbose]) { + NSLog(@"PluginResult toErrorCallbackString: %@", errorCB); + } + return errorCB; +} + +static BOOL gIsVerbose = NO; ++ (void)setVerbose:(BOOL)verbose +{ + gIsVerbose = verbose; +} + ++ (BOOL)isVerbose +{ + return gIsVerbose; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVReachability.h b/iPhone/CordovaLib/Classes/CDVReachability.h new file mode 100755 index 0000000..01a95c3 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVReachability.h @@ -0,0 +1,85 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachability APIs. + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + +*/ + +#import <Foundation/Foundation.h> +#import <SystemConfiguration/SystemConfiguration.h> +#import <netinet/in.h> + +typedef enum { + NotReachable = 0, + ReachableViaWWAN, // this value has been swapped with ReachableViaWiFi for Cordova backwards compat. reasons + ReachableViaWiFi // this value has been swapped with ReachableViaWWAN for Cordova backwards compat. reasons +} NetworkStatus; +#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" + +@interface CDVReachability : NSObject +{ + BOOL localWiFiRef; + SCNetworkReachabilityRef reachabilityRef; +} + +// reachabilityWithHostName- Use to check the reachability of a particular host name. ++ (CDVReachability*)reachabilityWithHostName:(NSString*)hostName; + +// reachabilityWithAddress- Use to check the reachability of a particular IP address. ++ (CDVReachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; + +// reachabilityForInternetConnection- checks whether the default route is available. +// Should be used by applications that do not connect to a particular host ++ (CDVReachability*)reachabilityForInternetConnection; + +// reachabilityForLocalWiFi- checks whether a local wifi connection is available. ++ (CDVReachability*)reachabilityForLocalWiFi; + +// Start listening for reachability notifications on the current run loop +- (BOOL)startNotifier; +- (void)stopNotifier; + +- (NetworkStatus)currentReachabilityStatus; +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +- (BOOL)connectionRequired; +@end diff --git a/iPhone/CordovaLib/Classes/CDVReachability.m b/iPhone/CordovaLib/Classes/CDVReachability.m new file mode 100755 index 0000000..3c5a48b --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVReachability.m @@ -0,0 +1,260 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachability APIs. + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + +*/ + +#import <sys/socket.h> +#import <netinet/in.h> +#import <netinet6/in6.h> +#import <arpa/inet.h> +#import <ifaddrs.h> +#import <netdb.h> + +#import <CoreFoundation/CoreFoundation.h> + +#import "CDVReachability.h" + +#define kShouldPrintReachabilityFlags 0 + +static void CDVPrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#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 + ); +#endif +} + +@implementation CDVReachability + +static void CDVReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + // NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + // NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + // Converted the asserts above to conditionals, with safe return from the function + if (info == NULL) { + NSLog(@"info was NULL in ReachabilityCallback"); + return; + } + + if (![(__bridge NSObject*) info isKindOfClass:[CDVReachability class]]) { + NSLog(@"info was wrong class in ReachabilityCallback"); + return; + } + + // We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someon uses the Reachability object in a different thread. + @autoreleasepool { + CDVReachability* noteObject = (__bridge CDVReachability*)info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification object:noteObject]; + } +} + +- (BOOL)startNotifier +{ + BOOL retVal = NO; + SCNetworkReachabilityContext context = {0, (__bridge void*)(self), NULL, NULL, NULL}; + + if (SCNetworkReachabilitySetCallback(reachabilityRef, CDVReachabilityCallback, &context)) { + if (SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { + retVal = YES; + } + } + return retVal; +} + +- (void)stopNotifier +{ + if (reachabilityRef != NULL) { + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +- (void)dealloc +{ + [self stopNotifier]; + if (reachabilityRef != NULL) { + CFRelease(reachabilityRef); + } +} + ++ (CDVReachability*)reachabilityWithHostName:(NSString*)hostName; +{ + CDVReachability* retVal = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if (reachability != NULL) { + retVal = [[self alloc] init]; + if (retVal != NULL) { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (CDVReachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + CDVReachability* retVal = NULL; + if (reachability != NULL) { + retVal = [[self alloc] init]; + if (retVal != NULL) { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (CDVReachability*)reachabilityForInternetConnection; +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + return [self reachabilityWithAddress:&zeroAddress]; +} + ++ (CDVReachability*)reachabilityForLocalWiFi; +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + CDVReachability* retVal = [self reachabilityWithAddress:&localWifiAddress]; + if (retVal != NULL) { + retVal->localWiFiRef = YES; + } + return retVal; +} + +#pragma mark Network Flag Handling + +- (NetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + CDVPrintReachabilityFlags(flags, "localWiFiStatusForFlags"); + + BOOL retVal = NotReachable; + if ((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) { + retVal = ReachableViaWiFi; + } + return retVal; +} + +- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + CDVPrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) { + // if target host is not reachable + return NotReachable; + } + + BOOL retVal = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = ReachableViaWiFi; + } + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || + ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))) { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) { + // ... and no [user] intervention is needed + retVal = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = ReachableViaWWAN; + } + return retVal; +} + +- (BOOL)connectionRequired; +{ + NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + return flags & kSCNetworkReachabilityFlagsConnectionRequired; + } + return NO; +} + +- (NetworkStatus)currentReachabilityStatus +{ + NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); + NetworkStatus retVal = NotReachable; + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + if (localWiFiRef) { + retVal = [self localWiFiStatusForFlags:flags]; + } else { + retVal = [self networkStatusForFlags:flags]; + } + } + return retVal; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVScreenOrientationDelegate.h b/iPhone/CordovaLib/Classes/CDVScreenOrientationDelegate.h new file mode 100755 index 0000000..7226205 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVScreenOrientationDelegate.h @@ -0,0 +1,28 @@ +/* + 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> + +@protocol CDVScreenOrientationDelegate <NSObject> + +- (NSUInteger)supportedInterfaceOrientations; +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; +- (BOOL)shouldAutorotate; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVSound.h b/iPhone/CordovaLib/Classes/CDVSound.h new file mode 100755 index 0000000..6551621 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVSound.h @@ -0,0 +1,110 @@ +/* + 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> +#import <AudioToolbox/AudioServices.h> +#import <AVFoundation/AVFoundation.h> + +#import "CDVPlugin.h" + +enum CDVMediaError { + MEDIA_ERR_ABORTED = 1, + MEDIA_ERR_NETWORK = 2, + MEDIA_ERR_DECODE = 3, + MEDIA_ERR_NONE_SUPPORTED = 4 +}; +typedef NSUInteger CDVMediaError; + +enum CDVMediaStates { + MEDIA_NONE = 0, + MEDIA_STARTING = 1, + MEDIA_RUNNING = 2, + MEDIA_PAUSED = 3, + MEDIA_STOPPED = 4 +}; +typedef NSUInteger CDVMediaStates; + +enum CDVMediaMsg { + MEDIA_STATE = 1, + MEDIA_DURATION = 2, + MEDIA_POSITION = 3, + MEDIA_ERROR = 9 +}; +typedef NSUInteger CDVMediaMsg; + +@interface CDVAudioPlayer : AVAudioPlayer +{ + NSString* mediaId; +} +@property (nonatomic, copy) NSString* mediaId; +@end + +@interface CDVAudioRecorder : AVAudioRecorder +{ + NSString* mediaId; +} +@property (nonatomic, copy) NSString* mediaId; +@end + +@interface CDVAudioFile : NSObject +{ + NSString* resourcePath; + NSURL* resourceURL; + CDVAudioPlayer* player; + CDVAudioRecorder* recorder; + NSNumber* volume; +} + +@property (nonatomic, strong) NSString* resourcePath; +@property (nonatomic, strong) NSURL* resourceURL; +@property (nonatomic, strong) CDVAudioPlayer* player; +@property (nonatomic, strong) NSNumber* volume; + +@property (nonatomic, strong) CDVAudioRecorder* recorder; + +@end + +@interface CDVSound : CDVPlugin <AVAudioPlayerDelegate, AVAudioRecorderDelegate> +{ + NSMutableDictionary* soundCache; + AVAudioSession* avSession; +} +@property (nonatomic, strong) NSMutableDictionary* soundCache; +@property (nonatomic, strong) AVAudioSession* avSession; + +- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)seekToAudio:(CDVInvokedUrlCommand*)command; +- (void)release:(CDVInvokedUrlCommand*)command; +- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command; + +- (BOOL)hasAudioSession; + +// helper methods +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId; +- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId; +- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message; + +- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command; +- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command; + +- (void)setVolume:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVSound.m b/iPhone/CordovaLib/Classes/CDVSound.m new file mode 100755 index 0000000..f7a3adf --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVSound.m @@ -0,0 +1,586 @@ +/* + 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 "CDVSound.h" +#import "CDVViewController.h" +#import "NSArray+Comparisons.h" + +#define DOCUMENTS_SCHEME_PREFIX @"documents://" +#define HTTP_SCHEME_PREFIX @"http://" +#define HTTPS_SCHEME_PREFIX @"https://" + +@implementation CDVSound + +@synthesize soundCache, avSession; + +// Maps a url for a resource path +// "Naked" resource paths are assumed to be from the www folder as its base +- (NSURL*)urlForResource:(NSString*)resourcePath +{ + NSURL* resourceURL = nil; + NSString* filePath = nil; + + // first try to find HTTP:// or Documents:// resources + + if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX] || [resourcePath hasPrefix:HTTPS_SCHEME_PREFIX]) { + // if it is a http url, use it + NSLog(@"Will use resource '%@' from the Internet.", resourcePath); + resourceURL = [NSURL URLWithString:resourcePath]; + } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", [CDVViewController applicationDocumentsDirectory]]]; + NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); + } else { + // attempt to find file path in www directory + filePath = [self.commandDelegate pathForResource:resourcePath]; + if (filePath != nil) { + NSLog(@"Found resource '%@' in the web folder.", filePath); + } else { + filePath = resourcePath; + NSLog(@"Will attempt to use file resource '%@'", filePath); + } + } + // check that file exists for all but HTTP_SHEME_PREFIX + if (filePath != nil) { + // try to access file + NSFileManager* fMgr = [[NSFileManager alloc] init]; + if (![fMgr fileExistsAtPath:filePath]) { + resourceURL = nil; + NSLog(@"Unknown resource '%@'", resourcePath); + } else { + // it's a valid file url, use it + resourceURL = [NSURL fileURLWithPath:filePath]; + } + } + return resourceURL; +} + +// Creates or gets the cached audio file resource object +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId +{ + BOOL bError = NO; + CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED; + NSString* errMsg = @""; + NSString* jsString = nil; + CDVAudioFile* audioFile = nil; + NSURL* resourceURL = nil; + + if ([self soundCache] == nil) { + [self setSoundCache:[NSMutableDictionary dictionaryWithCapacity:1]]; + } else { + audioFile = [[self soundCache] objectForKey:mediaId]; + } + if (audioFile == nil) { + // validate resourcePath and create + + if ((resourcePath == nil) || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]) { + bError = YES; + errcode = MEDIA_ERR_ABORTED; + errMsg = @"invalid media src argument"; + } else { + resourceURL = [self urlForResource:resourcePath]; + } + + if (resourceURL == nil) { + bError = YES; + errcode = MEDIA_ERR_ABORTED; + errMsg = [NSString stringWithFormat:@"Cannot use audio file from resource '%@'", resourcePath]; + } + if (bError) { + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, errcode]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:errcode message:errMsg]]; + [self.commandDelegate evalJs:jsString]; + } else { + audioFile = [[CDVAudioFile alloc] init]; + audioFile.resourcePath = resourcePath; + audioFile.resourceURL = resourceURL; + [[self soundCache] setObject:audioFile forKey:mediaId]; + } + } + return audioFile; +} + +// returns whether or not audioSession is available - creates it if necessary +- (BOOL)hasAudioSession +{ + BOOL bSession = YES; + + if (!self.avSession) { + NSError* error = nil; + + self.avSession = [AVAudioSession sharedInstance]; + if (error) { + // is not fatal if can't get AVAudioSession , just log the error + NSLog(@"error creating audio session: %@", [[error userInfo] description]); + self.avSession = nil; + bSession = NO; + } + } + return bSession; +} + +// helper function to create a error object string +- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message +{ + NSMutableDictionary* errorDict = [NSMutableDictionary dictionaryWithCapacity:2]; + + [errorDict setObject:[NSNumber numberWithUnsignedInt:code] forKey:@"code"]; + [errorDict setObject:message ? message:@"" forKey:@"message"]; + return [errorDict cdvjk_JSONString]; +} + +- (void)create:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command.arguments objectAtIndex:0]; + NSString* resourcePath = [command.arguments objectAtIndex:1]; + + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId]; + + if (audioFile == nil) { + NSString* errorMessage = [NSString stringWithFormat:@"Failed to initialize Media file with path %@", resourcePath]; + NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMessage]]; + [self.commandDelegate evalJs:jsString]; + } else { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } +} + +- (void)setVolume:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + NSString* mediaId = [command.arguments objectAtIndex:0]; + NSNumber* volume = [command.arguments objectAtIndex:1 withDefault:[NSNumber numberWithFloat:1.0]]; + + CDVAudioFile* audioFile; + if ([self soundCache] == nil) { + [self setSoundCache:[NSMutableDictionary dictionaryWithCapacity:1]]; + } else { + audioFile = [[self soundCache] objectForKey:mediaId]; + audioFile.volume = volume; + [[self soundCache] setObject:audioFile forKey:mediaId]; + } + + // don't care for any callbacks +} + +- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + NSString* mediaId = [command.arguments objectAtIndex:0]; + NSString* resourcePath = [command.arguments objectAtIndex:1]; + NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil]; + + BOOL bError = NO; + NSString* jsString = nil; + + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId]; + + if (audioFile != nil) { + if (audioFile.player == nil) { + bError = [self prepareToPlay:audioFile withId:mediaId]; + } + if (!bError) { + // audioFile.player != nil or player was successfully created + // get the audioSession and set the category to allow Playing when device is locked or ring/silent switch engaged + if ([self hasAudioSession]) { + NSError* __autoreleasing err = nil; + NSNumber* playAudioWhenScreenIsLocked = [options objectForKey:@"playAudioWhenScreenIsLocked"]; + BOOL bPlayAudioWhenScreenIsLocked = YES; + if (playAudioWhenScreenIsLocked != nil) { + bPlayAudioWhenScreenIsLocked = [playAudioWhenScreenIsLocked boolValue]; + } + + NSString* sessionCategory = bPlayAudioWhenScreenIsLocked ? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient; + [self.avSession setCategory:sessionCategory error:&err]; + if (![self.avSession setActive:YES error:&err]) { + // other audio with higher priority that does not allow mixing could cause this to fail + NSLog(@"Unable to play audio: %@", [err localizedFailureReason]); + bError = YES; + } + } + if (!bError) { + NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); + NSNumber* loopOption = [options objectForKey:@"numberOfLoops"]; + NSInteger numberOfLoops = 0; + if (loopOption != nil) { + numberOfLoops = [loopOption intValue] - 1; + } + audioFile.player.numberOfLoops = numberOfLoops; + if (audioFile.player.isPlaying) { + [audioFile.player stop]; + audioFile.player.currentTime = 0; + } + if (audioFile.volume != nil) { + audioFile.player.volume = [audioFile.volume floatValue]; + } + + [audioFile.player play]; + double position = round(audioFile.player.duration * 1000) / 1000; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_DURATION, position, @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING]; + [self.commandDelegate evalJs:jsString]; + } + } + if (bError) { + /* I don't see a problem playing previously recorded audio so removing this section - BG + NSError* error; + // try loading it one more time, in case the file was recorded previously + audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:audioFile.resourceURL error:&error]; + if (error != nil) { + NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error); + audioFile.player = nil; + } else { + NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); + audioFile.player.numberOfLoops = numberOfLoops; + [audioFile.player play]; + } */ + // error creating the session or player + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_NONE_SUPPORTED]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_NONE_SUPPORTED message:nil]]; + [self.commandDelegate evalJs:jsString]; + } + } + // else audioFile was nil - error already returned from audioFile for resource + return; +} + +- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId +{ + BOOL bError = NO; + NSError* __autoreleasing playerError = nil; + + // create the player + NSURL* resourceURL = audioFile.resourceURL; + + if ([resourceURL isFileURL]) { + audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:resourceURL error:&playerError]; + } else { + NSURLRequest* request = [NSURLRequest requestWithURL:resourceURL]; + NSURLResponse* __autoreleasing response = nil; + NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&playerError]; + if (playerError) { + NSLog(@"Unable to download audio from: %@", [resourceURL absoluteString]); + } else { + // 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]; + CFRelease(uuidString); + CFRelease(uuidRef); + + [data writeToFile:filePath atomically:YES]; + NSURL* fileURL = [NSURL fileURLWithPath:filePath]; + audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playerError]; + } + } + + if (playerError != nil) { + NSLog(@"Failed to initialize AVAudioPlayer: %@\n", [playerError localizedDescription]); + audioFile.player = nil; + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + bError = YES; + } else { + audioFile.player.mediaId = mediaId; + audioFile.player.delegate = self; + bError = ![audioFile.player prepareToPlay]; + } + return bError; +} + +- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command.arguments objectAtIndex:0]; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if ((audioFile != nil) && (audioFile.player != nil)) { + NSLog(@"Stopped playing audio sample '%@'", audioFile.resourcePath); + [audioFile.player stop]; + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + } // ignore if no media playing + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command.arguments objectAtIndex:0]; + NSString* jsString = nil; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + + if ((audioFile != nil) && (audioFile.player != nil)) { + NSLog(@"Paused playing audio sample '%@'", audioFile.resourcePath); + [audioFile.player pause]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_PAUSED]; + } + // ignore if no media playing + + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)seekToAudio:(CDVInvokedUrlCommand*)command +{ + // args: + // 0 = Media id + // 1 = path to resource + // 2 = seek to location in milliseconds + + NSString* mediaId = [command.arguments objectAtIndex:0]; + + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + double position = [[command.arguments objectAtIndex:1] doubleValue]; + + if ((audioFile != nil) && (audioFile.player != nil)) { + double posInSeconds = position / 1000; + audioFile.player.currentTime = posInSeconds; + NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)release:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command.arguments objectAtIndex:0]; + + if (mediaId != nil) { + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + if (audioFile != nil) { + if (audioFile.player && [audioFile.player isPlaying]) { + [audioFile.player stop]; + } + if (audioFile.recorder && [audioFile.recorder isRecording]) { + [audioFile.recorder stop]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + self.avSession = nil; + } + [[self soundCache] removeObjectForKey:mediaId]; + NSLog(@"Media with id %@ released", mediaId); + } + } +} + +- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* mediaId = [command.arguments objectAtIndex:0]; + +#pragma unused(mediaId) + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + double position = -1; + + if ((audioFile != nil) && (audioFile.player != nil) && [audioFile.player isPlaying]) { + position = round(audioFile.player.currentTime * 1000) / 1000; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:position]; + NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, position, [result toSuccessCallbackString:callbackId]]; + [self.commandDelegate evalJs:jsString]; +} + +- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + + NSString* mediaId = [command.arguments objectAtIndex:0]; + CDVAudioFile* audioFile = [self audioFileForResource:[command.arguments objectAtIndex:1] withId:mediaId]; + NSString* jsString = nil; + NSString* errorMsg = @""; + + if (audioFile != nil) { + NSError* __autoreleasing error = nil; + + if (audioFile.recorder != nil) { + [audioFile.recorder stop]; + audioFile.recorder = nil; + } + // get the audioSession and set the category to allow recording when device is locked or ring/silent switch engaged + if ([self hasAudioSession]) { + [self.avSession setCategory:AVAudioSessionCategoryRecord error:nil]; + if (![self.avSession setActive:YES error:&error]) { + // other audio with higher priority that does not allow mixing could cause this to fail + errorMsg = [NSString stringWithFormat:@"Unable to record audio: %@", [error localizedFailureReason]]; + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_ABORTED]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; + [self.commandDelegate evalJs:jsString]; + return; + } + } + + // create a new recorder for each start record + audioFile.recorder = [[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error]; + + bool recordingSuccess = NO; + if (error == nil) { + audioFile.recorder.delegate = self; + audioFile.recorder.mediaId = mediaId; + recordingSuccess = [audioFile.recorder record]; + if (recordingSuccess) { + NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath); + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING]; + } + } + + if ((error != nil) || (recordingSuccess == NO)) { + if (error != nil) { + errorMsg = [NSString stringWithFormat:@"Failed to initialize AVAudioRecorder: %@\n", [error localizedFailureReason]]; + } else { + errorMsg = @"Failed to start recording using AVAudioRecorder"; + } + audioFile.recorder = nil; + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; + } + } else { + // file does not exist + NSLog(@"Could not start recording audio, file '%@' does not exist.", audioFile.resourcePath); + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:@"File to record to does not exist"]]; + } + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } + return; +} + +- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command.arguments objectAtIndex:0]; + + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if ((audioFile != nil) && (audioFile.recorder != nil)) { + NSLog(@"Stopped recording audio sample '%@'", audioFile.resourcePath); + [audioFile.recorder stop]; + // no callback - that will happen in audioRecorderDidFinishRecording + } + // ignore if no media recording + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag +{ + CDVAudioRecorder* aRecorder = (CDVAudioRecorder*)recorder; + NSString* mediaId = aRecorder.mediaId; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if (audioFile != nil) { + NSLog(@"Finished recording audio sample '%@'", audioFile.resourcePath); + } + if (flag) { + 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]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + [self.commandDelegate evalJs:jsString]; +} + +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag +{ + CDVAudioPlayer* aPlayer = (CDVAudioPlayer*)player; + NSString* mediaId = aPlayer.mediaId; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if (audioFile != nil) { + NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath); + } + if (flag) { + 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]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + [self.commandDelegate evalJs:jsString]; +} + +- (void)onMemoryWarning +{ + [[self soundCache] removeAllObjects]; + [self setSoundCache:nil]; + [self setAvSession:nil]; + + [super onMemoryWarning]; +} + +- (void)dealloc +{ + [[self soundCache] removeAllObjects]; +} + +- (void)onReset +{ + for (CDVAudioFile* audioFile in [[self soundCache] allValues]) { + if (audioFile != nil) { + if (audioFile.player != nil) { + [audioFile.player stop]; + audioFile.player.currentTime = 0; + } + if (audioFile.recorder != nil) { + [audioFile.recorder stop]; + } + } + } + + [[self soundCache] removeAllObjects]; +} + +@end + +@implementation CDVAudioFile + +@synthesize resourcePath; +@synthesize resourceURL; +@synthesize player, volume; +@synthesize recorder; + +@end +@implementation CDVAudioPlayer +@synthesize mediaId; + +@end + +@implementation CDVAudioRecorder +@synthesize mediaId; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.h b/iPhone/CordovaLib/Classes/CDVSplashScreen.h new file mode 100755 index 0000000..b0d8615 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.h @@ -0,0 +1,28 @@ +/* + 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> +#import "CDVPlugin.h" + +@interface CDVSplashScreen : CDVPlugin {} + +- (void)show:(CDVInvokedUrlCommand*)command; +- (void)hide:(CDVInvokedUrlCommand*)command; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.m b/iPhone/CordovaLib/Classes/CDVSplashScreen.m new file mode 100755 index 0000000..2512328 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.m @@ -0,0 +1,49 @@ +/* + 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 "CDVSplashScreen.h" +#import "CDVViewController.h" + +@implementation CDVSplashScreen + +- (void)__show:(BOOL)show +{ + // Legacy support - once deprecated classes removed, clean this up + id <UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate]; + + if ([delegate respondsToSelector:@selector(viewController)]) { + id vc = [delegate performSelector:@selector(viewController)]; + if ([vc isKindOfClass:[CDVViewController class]]) { + ((CDVViewController*)vc).imageView.hidden = !show; + ((CDVViewController*)vc).activityView.hidden = !show; + } + } +} + +- (void)show:(CDVInvokedUrlCommand*)command +{ + [self __show:YES]; +} + +- (void)hide:(CDVInvokedUrlCommand*)command +{ + [self __show:NO]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVURLProtocol.h b/iPhone/CordovaLib/Classes/CDVURLProtocol.h new file mode 100755 index 0000000..ce8df38 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.h @@ -0,0 +1,32 @@ +/* + 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> +#import "CDVAvailability.h" + +@class CDVViewController; + +@interface CDVURLProtocol : NSURLProtocol {} + ++ (void)registerPGHttpURLProtocol CDV_DEPRECATED (2.0, "This is now a no-op and should be removed."); ++ (void)registerURLProtocol CDV_DEPRECATED (2.0, "This is now a no-op and should be removed."); + ++ (void)registerViewController:(CDVViewController*)viewController; ++ (void)unregisterViewController:(CDVViewController*)viewController; +@end diff --git a/iPhone/CordovaLib/Classes/CDVURLProtocol.m b/iPhone/CordovaLib/Classes/CDVURLProtocol.m new file mode 100755 index 0000000..a645288 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.m @@ -0,0 +1,209 @@ +/* + 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 "CDVURLProtocol.h" +#import "CDVCommandQueue.h" +#import "CDVWhitelist.h" +#import "CDVViewController.h" + +@interface CDVHTTPURLResponse : NSHTTPURLResponse +- (id)initWithUnauthorizedURL:(NSURL*)url; +- (id)initWithBlankResponse:(NSURL*)url; +@property (nonatomic) NSInteger statusCode; +@end + +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; + +// Returns the registered view controller that sent the given request. +// If the user-agent is not from a UIWebView, or if it's from an unregistered one, +// then nil is returned. +static CDVViewController *viewControllerForRequest(NSURLRequest* request) +{ + // The exec bridge explicitly sets the VC address in a header. + // This works around the User-Agent not being set for file: URLs. + NSString* addrString = [request valueForHTTPHeaderField:@"vc"]; + + if (addrString == nil) { + NSString* userAgent = [request valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent == nil) { + return nil; + } + NSUInteger bracketLocation = [userAgent rangeOfString:@"(" options:NSBackwardsSearch].location; + if (bracketLocation == NSNotFound) { + return nil; + } + addrString = [userAgent substringFromIndex:bracketLocation + 1]; + } + + long long viewControllerAddress = [addrString longLongValue]; + @synchronized(gRegisteredControllers) { + if (![gRegisteredControllers containsObject:[NSNumber numberWithLongLong:viewControllerAddress]]) { + return nil; + } + } + + return (__bridge CDVViewController*)(void*)viewControllerAddress; +} + +@implementation CDVURLProtocol + ++ (void)registerPGHttpURLProtocol {} + ++ (void)registerURLProtocol {} + +// Called to register the URLProtocol, and to make it away of an instance of +// a ViewController. ++ (void)registerViewController:(CDVViewController*)viewController +{ + if (gRegisteredControllers == nil) { + [NSURLProtocol registerClass:[CDVURLProtocol class]]; + gRegisteredControllers = [[NSMutableSet alloc] initWithCapacity:8]; + // The whitelist doesn't change, so grab the first one and store it. + gWhitelist = viewController.whitelist; + + // Note that we grab the whitelist from the first viewcontroller for now - but this will change + // when we allow a registered viewcontroller to have its own whitelist (e.g InAppBrowser) + // Differentiating the requests will be through the 'vc' http header below as used for the js->objc bridge. + // The 'vc' value is generated by casting the viewcontroller object to a (long long) value (see CDVViewController::webViewDidFinishLoad) + if (gWhitelist == nil) { + NSLog(@"WARNING: NO whitelist has been set in CDVURLProtocol."); + } + } + + @synchronized(gRegisteredControllers) { + [gRegisteredControllers addObject:[NSNumber numberWithLongLong:(long long)viewController]]; + } +} + ++ (void)unregisterViewController:(CDVViewController*)viewController +{ + @synchronized(gRegisteredControllers) { + [gRegisteredControllers removeObject:[NSNumber numberWithLongLong:(long long)viewController]]; + } +} + ++ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest +{ + NSURL* theUrl = [theRequest URL]; + CDVViewController* viewController = viewControllerForRequest(theRequest); + + if (viewController != nil) { + if ([[theUrl path] isEqualToString:@"/!gap_exec"]) { + NSString* queuedCommandsJSON = [theRequest valueForHTTPHeaderField:@"cmds"]; + NSString* requestId = [theRequest valueForHTTPHeaderField:@"rc"]; + if (requestId == nil) { + NSLog(@"!cordova request missing rc header"); + return NO; + } + BOOL hasCmds = [queuedCommandsJSON length] > 0; + if (hasCmds) { + SEL sel = @selector(enqueCommandBatch:); + [viewController.commandQueue performSelectorOnMainThread:sel withObject:queuedCommandsJSON waitUntilDone:NO]; + } else { + SEL sel = @selector(maybeFetchCommandsFromJs:); + [viewController.commandQueue performSelectorOnMainThread:sel withObject:[NSNumber numberWithInteger:[requestId integerValue]] waitUntilDone:NO]; + } + // Returning NO here would be 20% faster, but it spams WebInspector's console with failure messages. + // If JS->Native bridge speed is really important for an app, they should use the iframe bridge. + // Returning YES here causes the request to come through canInitWithRequest two more times. + // For this reason, we return NO when cmds exist. + return !hasCmds; + } + // we only care about http and https connections. + // CORS takes care of http: trying to access file: URLs. + if ([gWhitelist schemeIsAllowed:[theUrl scheme]]) { + // if it FAILS the whitelist, we return TRUE, so we can fail the connection later + return ![gWhitelist URLIsAllowed:theUrl]; + } + } + + return NO; +} + ++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request +{ + // NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd)); + return request; +} + +- (void)startLoading +{ + // NSLog(@"%@ received %@ - start", self, NSStringFromSelector(_cmd)); + NSURL* url = [[self request] URL]; + + if ([[url path] isEqualToString:@"/!gap_exec"]) { + CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithBlankResponse:url]; + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [[self client] URLProtocolDidFinishLoading:self]; + return; + } + + NSString* body = [gWhitelist errorStringForURL:url]; + + CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithUnauthorizedURL:url]; + + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + + [[self client] URLProtocol:self didLoadData:[body dataUsingEncoding:NSASCIIStringEncoding]]; + + [[self client] URLProtocolDidFinishLoading:self]; +} + +- (void)stopLoading +{ + // do any cleanup here +} + ++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB +{ + return NO; +} + +@end + +@implementation CDVHTTPURLResponse +@synthesize statusCode; + +- (id)initWithUnauthorizedURL:(NSURL*)url +{ + self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; + if (self) { + self.statusCode = 401; + } + return self; +} + +- (id)initWithBlankResponse:(NSURL*)url +{ + self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; + if (self) { + self.statusCode = 200; + } + return self; +} + +- (NSDictionary*)allHeaderFields +{ + return nil; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVViewController.h b/iPhone/CordovaLib/Classes/CDVViewController.h new file mode 100755 index 0000000..8318398 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVViewController.h @@ -0,0 +1,79 @@ +/* + 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 "CDVCordovaView.h" + +#import "JSONKit.h" +#import "CDVAvailability.h" +#import "CDVInvokedUrlCommand.h" +#import "CDVCommandDelegate.h" +#import "CDVWhitelist.h" +#import "CDVScreenOrientationDelegate.h" + +@class CDVCommandQueue; +@class CDVCommandDelegateImpl; + +@interface CDVViewController : UIViewController <UIWebViewDelegate, CDVScreenOrientationDelegate>{ + @protected + CDVCommandDelegateImpl* _commandDelegate; + @protected + CDVCommandQueue* _commandQueue; + NSString* _userAgent; +} + +@property (nonatomic, strong) IBOutlet CDVCordovaView* webView; + +@property (nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; +@property (nonatomic, readonly, strong) NSDictionary* pluginsMap; +@property (nonatomic, readonly, strong) NSDictionary* settings; +@property (nonatomic, readonly, strong) NSXMLParser* configParser; +@property (nonatomic, readonly, strong) CDVWhitelist* whitelist; // readonly for public +@property (nonatomic, readonly, assign) BOOL loadFromString; +@property (nonatomic, readwrite, copy)NSString * invokeString CDV_DEPRECATED(2.0, "Use window.handleOpenURL(url instead. It is called when the app is launched through a custom scheme url."); + +@property (nonatomic, readwrite, assign) BOOL useSplashScreen; +@property (nonatomic, readonly, strong) IBOutlet UIActivityIndicatorView* activityView; +@property (nonatomic, readonly, strong) UIImageView* imageView; + +@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) NSString* userAgent; + ++ (NSDictionary*)getBundlePlist:(NSString*)plistName; ++ (NSString*)applicationDocumentsDirectory; ++ (NSString*)originalUserAgent; + +- (void)printMultitaskingInfo; +- (void)createGapView; +- (CDVCordovaView*)newCordovaViewWithFrame:(CGRect)bounds; + +- (void)javascriptAlert:(NSString*)text; +- (NSString*)appURLScheme; + +- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations; +- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation; + +- (id)getCommandInstance:(NSString*)pluginName; +- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className; + +- (BOOL)URLisAllowed:(NSURL*)url; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVViewController.m b/iPhone/CordovaLib/Classes/CDVViewController.m new file mode 100755 index 0000000..f1b36df --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVViewController.m @@ -0,0 +1,982 @@ +/* + 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 <objc/message.h> +#import "CDV.h" +#import "CDVCommandQueue.h" +#import "CDVCommandDelegateImpl.h" +#import "CDVConfigParser.h" + +#define degreesToRadian(x) (M_PI * (x) / 180.0) +#define CDV_USER_AGENT_KEY @"Cordova-User-Agent" +#define CDV_USER_AGENT_VERSION_KEY @"Cordova-User-Agent-Version" + +static NSString* gOriginalUserAgent = nil; + +@interface CDVViewController () + +@property (nonatomic, readwrite, strong) NSXMLParser* configParser; +@property (nonatomic, readwrite, strong) NSDictionary* settings; +@property (nonatomic, readwrite, strong) CDVWhitelist* whitelist; +@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects; +@property (nonatomic, readwrite, strong) NSDictionary* pluginsMap; +@property (nonatomic, readwrite, strong) NSArray* supportedOrientations; +@property (nonatomic, readwrite, assign) BOOL loadFromString; + +@property (nonatomic, readwrite, strong) IBOutlet UIActivityIndicatorView* activityView; +@property (nonatomic, readwrite, strong) UIImageView* imageView; +@property (readwrite, assign) BOOL initialized; + +@end + +@implementation CDVViewController + +@synthesize webView, supportedOrientations; +@synthesize pluginObjects, pluginsMap, whitelist; +@synthesize configParser, settings, loadFromString; +@synthesize imageView, activityView, useSplashScreen; +@synthesize wwwFolderName, startPage, invokeString, initialized; +@synthesize commandDelegate = _commandDelegate; +@synthesize commandQueue = _commandQueue; + +- (void)__init +{ + if ((self != nil) && !self.initialized) { + _commandQueue = [[CDVCommandQueue alloc] initWithViewController:self]; + _commandDelegate = [[CDVCommandDelegateImpl alloc] initWithViewController:self]; + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedOrientationChange) + name:UIDeviceOrientationDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:) + name:UIApplicationWillTerminateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:) + name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:) + name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppLocaleDidChange:) + name:NSCurrentLocaleDidChangeNotification object:nil]; + + if (IsAtLeastiOSVersion(@"4.0")) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification object:nil]; + } + + // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist + self.supportedOrientations = [self parseInterfaceOrientations: + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]]; + + self.wwwFolderName = @"www"; + self.startPage = @"index.html"; + + [self printMultitaskingInfo]; + [self printDeprecationNotice]; + self.initialized = YES; + + // load config.xml settings + [self loadSettings]; + } +} + +- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + [self __init]; + return self; +} + +- (id)init +{ + self = [super init]; + [self __init]; + return self; +} + +- (void)printDeprecationNotice +{ + if (!IsAtLeastiOSVersion(@"5.0")) { + NSLog(@"CRITICAL: For Cordova 2.0, you will need to upgrade to at least iOS 5.0 or greater. Your current version of iOS is %@.", + [[UIDevice currentDevice] systemVersion] + ); + } +} + +- (void)printMultitaskingInfo +{ + UIDevice* device = [UIDevice currentDevice]; + BOOL backgroundSupported = NO; + + if ([device respondsToSelector:@selector(isMultitaskingSupported)]) { + backgroundSupported = device.multitaskingSupported; + } + + NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"]; + if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default) + exitsOnSuspend = [NSNumber numberWithBool:NO]; + } + + NSLog(@"Multi-tasking -> Device: %@, App: %@", (backgroundSupported ? @"YES" : @"NO"), (![exitsOnSuspend intValue]) ? @"YES" : @"NO"); +} + +- (BOOL)URLisAllowed:(NSURL*)url +{ + if (self.whitelist == nil) { + return YES; + } + + return [self.whitelist URLIsAllowed:url]; +} + +- (void)loadSettings +{ + CDVConfigParser* delegate = [[CDVConfigParser alloc] init]; + + // read from config.xml in the app bundle + NSString* path = [[NSBundle mainBundle] pathForResource:@"config" ofType:@"xml"]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSAssert(NO, @"ERROR: config.xml does not exist. Please run cordova-ios/bin/cordova_plist_to_config_xml path/to/project."); + return; + } + + NSURL* url = [NSURL fileURLWithPath:path]; + + configParser = [[NSXMLParser alloc] initWithContentsOfURL:url]; + if (configParser == nil) { + NSLog(@"Failed to initialize XML parser."); + return; + } + [configParser setDelegate:((id < NSXMLParserDelegate >)delegate)]; + [configParser parse]; + + // Get the plugin dictionary, whitelist and settings from the delegate. + self.pluginsMap = [delegate.pluginsDict dictionaryWithLowercaseKeys]; + self.whitelist = [[CDVWhitelist alloc] initWithArray:delegate.whitelistHosts]; + self.settings = delegate.settings; + + // Initialize the plugin objects dict. + self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:4]; +} + +// Implement viewDidLoad to do additional setup after loading the view, typically from a nib. +- (void)viewDidLoad +{ + [super viewDidLoad]; + + NSURL* appURL = nil; + NSString* loadErr = nil; + + if ([self.startPage rangeOfString:@"://"].location != NSNotFound) { + appURL = [NSURL URLWithString:self.startPage]; + } else if ([self.wwwFolderName rangeOfString:@"://"].location != NSNotFound) { + appURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.wwwFolderName, self.startPage]]; + } else { + NSString* startFilePath = [self.commandDelegate pathForResource:self.startPage]; + if (startFilePath == nil) { + loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage]; + NSLog(@"%@", loadErr); + self.loadFromString = YES; + appURL = nil; + } else { + appURL = [NSURL fileURLWithPath:startFilePath]; + } + } + + // // Fix the iOS 5.1 SECURITY_ERR bug (CB-347), this must be before the webView is instantiated //// + + NSString* backupWebStorageType = @"cloud"; // default value + + id backupWebStorage = [self.settings objectForKey:@"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"; + } + + if (IsAtLeastiOSVersion(@"5.1")) { + [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType]; + } + + // // Instantiate the WebView /////////////// + + [self createGapView]; + + // ///////////////// + + NSNumber* enableLocation = [self.settings objectForKey:@"EnableLocation"]; + NSString* enableViewportScale = [self.settings objectForKey:@"EnableViewportScale"]; + NSNumber* allowInlineMediaPlayback = [self.settings objectForKey:@"AllowInlineMediaPlayback"]; + BOOL mediaPlaybackRequiresUserAction = YES; // default value + if ([self.settings objectForKey:@"MediaPlaybackRequiresUserAction"]) { + mediaPlaybackRequiresUserAction = [(NSNumber*)[settings objectForKey:@"MediaPlaybackRequiresUserAction"] boolValue]; + } + + self.webView.scalesPageToFit = [enableViewportScale boolValue]; + + /* + * Fire up the GPS Service right away as it takes a moment for data to come back. + */ + + if ([enableLocation boolValue]) { + [[self.commandDelegate getCommandInstance:@"Geolocation"] getLocation:[CDVInvokedUrlCommand new]]; + } + + /* + * 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")))) { + [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView settings:[NSDictionary dictionaryWithObjectsAndKeys: + @"backupType", backupWebStorageType, nil]] withClassName:NSStringFromClass([CDVLocalStorage class])]; + } + + /* + * This is for iOS 4.x, where you can allow inline <video> and <audio>, and also autoplay them + */ + if ([allowInlineMediaPlayback boolValue] && [self.webView respondsToSelector:@selector(allowsInlineMediaPlayback)]) { + self.webView.allowsInlineMediaPlayback = YES; + } + if ((mediaPlaybackRequiresUserAction == NO) && [self.webView respondsToSelector:@selector(mediaPlaybackRequiresUserAction)]) { + self.webView.mediaPlaybackRequiresUserAction = NO; + } + + // UIWebViewBounce property - defaults to true + NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"]; + BOOL bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]); + + // prevent webView from bouncing + // based on UIWebViewBounce key in config.xml + if (!bounceAllowed) { + if ([self.webView respondsToSelector:@selector(scrollView)]) { + ((UIScrollView*)[self.webView scrollView]).bounces = NO; + } else { + for (id subview in self.webView.subviews) { + if ([[subview class] isSubclassOfClass:[UIScrollView class]]) { + ((UIScrollView*)subview).bounces = NO; + } + } + } + } + + /* + * iOS 6.0 UIWebView properties + */ + if (IsAtLeastiOSVersion(@"6.0")) { + BOOL keyboardDisplayRequiresUserAction = YES; // KeyboardDisplayRequiresUserAction - defaults to YES + if ([self.settings objectForKey:@"KeyboardDisplayRequiresUserAction"] != nil) { + if ([self.settings objectForKey:@"KeyboardDisplayRequiresUserAction"]) { + keyboardDisplayRequiresUserAction = [(NSNumber*)[self.settings objectForKey:@"KeyboardDisplayRequiresUserAction"] boolValue]; + } + } + + // property check for compiling under iOS < 6 + if ([self.webView respondsToSelector:@selector(setKeyboardDisplayRequiresUserAction:)]) { + [self.webView setValue:[NSNumber numberWithBool:keyboardDisplayRequiresUserAction] forKey:@"keyboardDisplayRequiresUserAction"]; + } + + BOOL suppressesIncrementalRendering = NO; // SuppressesIncrementalRendering - defaults to NO + if ([self.settings objectForKey:@"SuppressesIncrementalRendering"] != nil) { + if ([self.settings objectForKey:@"SuppressesIncrementalRendering"]) { + suppressesIncrementalRendering = [(NSNumber*)[self.settings objectForKey:@"SuppressesIncrementalRendering"] boolValue]; + } + } + + // property check for compiling under iOS < 6 + if ([self.webView respondsToSelector:@selector(setSuppressesIncrementalRendering:)]) { + [self.webView setValue:[NSNumber numberWithBool:suppressesIncrementalRendering] forKey:@"suppressesIncrementalRendering"]; + } + } + + // ///////////////// + + 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]; + } +} + +- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations +{ + NSMutableArray* result = [[NSMutableArray alloc] init]; + + if (orientations != nil) { + NSEnumerator* enumerator = [orientations objectEnumerator]; + NSString* orientationString; + + while (orientationString = [enumerator nextObject]) { + if ([orientationString isEqualToString:@"UIInterfaceOrientationPortrait"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationPortraitUpsideDown"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeLeft"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]]; + } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeRight"]) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight]]; + } + } + } + + // default + if ([result count] == 0) { + [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]]; + } + + return result; +} + +- (NSInteger)mapIosOrientationToJsOrientation:(UIInterfaceOrientation)orientation +{ + switch (orientation) { + case UIInterfaceOrientationPortraitUpsideDown: + return 180; + + case UIInterfaceOrientationLandscapeLeft: + return -90; + + case UIInterfaceOrientationLandscapeRight: + return 90; + + case UIInterfaceOrientationPortrait: + return 0; + + default: + return 0; + } +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // First, ask the webview via JS if it supports the new orientation + NSString* jsCall = [NSString stringWithFormat: + @"window.shouldRotateToOrientation && window.shouldRotateToOrientation(%d);" + , [self mapIosOrientationToJsOrientation:interfaceOrientation]]; + NSString* res = [webView stringByEvaluatingJavaScriptFromString:jsCall]; + + if ([res length] > 0) { + return [res boolValue]; + } + + // if js did not handle the new orientation (no return value), use values from the plist (via supportedOrientations) + return [self supportsOrientation:interfaceOrientation]; +} + +- (BOOL)shouldAutorotate +{ + return YES; +} + +- (NSUInteger)supportedInterfaceOrientations +{ + NSUInteger ret = 0; + + if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) { + ret = ret | (1 << UIInterfaceOrientationPortrait); + } + if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown]) { + ret = ret | (1 << UIInterfaceOrientationPortraitUpsideDown); + } + if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]) { + ret = ret | (1 << UIInterfaceOrientationLandscapeRight); + } + if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeLeft]) { + ret = ret | (1 << UIInterfaceOrientationLandscapeLeft); + } + + return ret; +} + +- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation +{ + return [self.supportedOrientations containsObject:[NSNumber numberWithInt:orientation]]; +} + +/** + Called by UIKit when the device starts to rotate to a new orientation. This fires the \c setOrientation + method on the Orientation object in JavaScript. Look at the JavaScript documentation for more information. + */ +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + if (!IsAtLeastiOSVersion(@"5.0")) { + NSString* jsCallback = [NSString stringWithFormat: + @"window.__defineGetter__('orientation',function(){ return %d; }); \ + cordova.fireWindowEvent('orientationchange');" + , [self mapIosOrientationToJsOrientation:fromInterfaceOrientation]]; + [self.commandDelegate evalJs:jsCallback]; + } +} + +- (CDVCordovaView*)newCordovaViewWithFrame:(CGRect)bounds +{ + return [[CDVCordovaView alloc] initWithFrame:bounds]; +} + ++ (NSString*)originalUserAgent +{ + if (gOriginalUserAgent == nil) { + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; + NSString* localeStr = [[NSLocale currentLocale] localeIdentifier]; + NSString* systemAndLocale = [NSString stringWithFormat:@"%@ %@", systemVersion, localeStr]; + + NSString* cordovaUserAgentVersion = [userDefaults stringForKey:CDV_USER_AGENT_VERSION_KEY]; + gOriginalUserAgent = [userDefaults stringForKey:CDV_USER_AGENT_KEY]; + BOOL cachedValueIsOld = ![systemAndLocale isEqualToString:cordovaUserAgentVersion]; + + if ((gOriginalUserAgent == nil) || cachedValueIsOld) { + UIWebView* sampleWebView = [[UIWebView alloc] initWithFrame:CGRectZero]; + gOriginalUserAgent = [sampleWebView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + + [userDefaults setObject:gOriginalUserAgent forKey:CDV_USER_AGENT_KEY]; + [userDefaults setObject:systemAndLocale forKey:CDV_USER_AGENT_VERSION_KEY]; + + [userDefaults synchronize]; + } + } + return gOriginalUserAgent; +} + +- (NSString*)userAgent +{ + if (_userAgent == nil) { + NSString* originalUserAgent = [[self class] originalUserAgent]; + // Use our address as a unique number to append to the User-Agent. + _userAgent = [NSString stringWithFormat:@"%@ (%lld)", originalUserAgent, (long long)self]; + } + return _userAgent; +} + +- (void)createGapView +{ + CGRect webViewBounds = self.view.bounds; + + webViewBounds.origin = self.view.bounds.origin; + + if (!self.webView) { + // setting the UserAgent must occur before the UIWebView is instantiated. + // This is read per instantiation, so it does not affect the main Cordova UIWebView + NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:self.userAgent, @"UserAgent", nil]; + [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; + + self.webView = [self newCordovaViewWithFrame:webViewBounds]; + self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + [self.view addSubview:self.webView]; + [self.view sendSubviewToBack:self.webView]; + + self.webView.delegate = self; + + // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set + [CDVURLProtocol registerViewController:self]; + } +} + +- (void)didReceiveMemoryWarning +{ + // iterate through all the plugin objects, and call hasPendingOperation + // if at least one has a pending operation, we don't call [super didReceiveMemoryWarning] + + NSEnumerator* enumerator = [self.pluginObjects objectEnumerator]; + CDVPlugin* plugin; + + BOOL doPurge = YES; + + while ((plugin = [enumerator nextObject])) { + if (plugin.hasPendingOperation) { + NSLog(@"Plugin '%@' has a pending operation, memory purge is delayed for didReceiveMemoryWarning.", NSStringFromClass([plugin class])); + doPurge = NO; + } + } + + if (doPurge) { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + } + + // Release any cached data, images, etc. that aren't in use. +} + +- (void)viewDidUnload +{ + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; + + self.webView.delegate = nil; + self.webView = nil; +} + +#pragma mark UIWebViewDelegate + +/** + When web application loads Add stuff to the DOM, mainly the user-defined settings from the Settings.plist file, and + the device's data such as device ID, platform version, etc. + */ +- (void)webViewDidStartLoad:(UIWebView*)theWebView +{ + [_commandQueue resetRequestId]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:nil]]; +} + +/** + Called when the webview finishes loading. This stops the activity view and closes the imageview + */ +- (void)webViewDidFinishLoad:(UIWebView*)theWebView +{ + /* + * Hide the Top Activity THROBBER in the Battery Bar + */ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + + id autoHideSplashScreenValue = [self.settings objectForKey:@"AutoHideSplashScreen"]; + // if value is missing, default to yes + if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { + self.imageView.hidden = YES; + self.activityView.hidden = YES; + [self.view.superview bringSubviewToFront:self.webView]; + } + [self didRotateFromInterfaceOrientation:(UIInterfaceOrientation)[[UIDevice currentDevice] orientation]]; + + // 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;}"; + [self.commandDelegate evalJs:nativeReady]; +} + +- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error +{ + NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]); + + /* + if ([error code] != NSURLErrorCancelled) + alert([error localizedDescription]); + */ +} + +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSURL* url = [request URL]; + + /* + * Execute any commands queued with cordova.exec() on the JS side. + * The part of the URL after gap:// is irrelevant. + */ + if ([[url scheme] isEqualToString:@"gap"]) { + [_commandQueue fetchCommandsFromJs]; + return NO; + } + + /* + * If a URL is being loaded that's a file/http/https URL, just load it internally + */ + else if ([url isFileURL]) { + return YES; + } + + /* + * If we loaded the HTML from a string, we let the app handle it + */ + else if (self.loadFromString == YES) { + self.loadFromString = NO; + return YES; + } + + /* + * all tel: scheme urls we let the UIWebview handle it using the default behavior + */ + else if ([[url scheme] isEqualToString:@"tel"]) { + return YES; + } + + /* + * all about: scheme urls are not handled + */ + else if ([[url scheme] isEqualToString:@"about"]) { + return NO; + } + + /* + * all data: scheme urls are handled + */ + else if ([[url scheme] isEqualToString:@"data"]) { + return YES; + } + + /* + * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. + */ + else { + // BOOL isIFrame = ([theWebView.request.mainDocumentURL absoluteString] == nil); + + if ([self.whitelist schemeIsAllowed:[url scheme]]) { + return [self.whitelist URLIsAllowed:url]; + } else { + if ([[UIApplication sharedApplication] canOpenURL:url]) { + [[UIApplication sharedApplication] openURL:url]; + } else { // handle any custom schemes to plugins + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + } + } + + return NO; + } + + return YES; +} + +#pragma mark GapHelpers + +- (void)javascriptAlert:(NSString*)text +{ + NSString* jsString = [NSString stringWithFormat:@"alert('%@');", text]; + + [self.commandDelegate evalJs:jsString]; +} + ++ (NSString*)resolveImageResource:(NSString*)resource +{ + NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; + BOOL isLessThaniOS4 = ([systemVersion compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending); + + // the iPad image (nor retina) differentiation code was not in 3.x, and we have to explicitly set the path + if (isLessThaniOS4) { + if (CDV_IsIPad()) { + return [NSString stringWithFormat:@"%@~ipad.png", resource]; + } else { + return [NSString stringWithFormat:@"%@.png", resource]; + } + } + + return resource; +} + ++ (NSString*)applicationDocumentsDirectory +{ + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString* basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; + + return basePath; +} + +- (void)showSplashScreen +{ + CGRect screenBounds = [[UIScreen mainScreen] bounds]; + NSString* launchImageFile = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; + + if (launchImageFile == nil) { // fallback if no launch image was specified + if (CDV_IsIPhone5()) { + // iPhone 5 or iPod Touch 6th-gen + launchImageFile = @"Default-568h"; + } else { + launchImageFile = @"Default"; + } + } + + NSString* orientedLaunchImageFile = nil; + CGAffineTransform startupImageTransform = CGAffineTransformIdentity; + UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation; + CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; + UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation; + UIImage* launchImage = nil; + + // default to center of screen as in the original implementation. This will produce the 20px jump + CGPoint center = CGPointMake((screenBounds.size.width / 2), (screenBounds.size.height / 2)); + + if (CDV_IsIPad()) { + if (!UIDeviceOrientationIsValidInterfaceOrientation(deviceOrientation)) { + deviceOrientation = (UIDeviceOrientation)statusBarOrientation; + } + + switch (deviceOrientation) { + case UIDeviceOrientationLandscapeLeft: // this is where the home button is on the right (yeah, I know, confusing) + { + orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile]; + startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(90)); + center.x -= MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; + } + break; + + case UIDeviceOrientationLandscapeRight: // this is where the home button is on the left (yeah, I know, confusing) + { + orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile]; + startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(-90)); + center.x += MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; + } + break; + + case UIDeviceOrientationPortraitUpsideDown: + { + orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile]; + startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(180)); + center.y -= MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; + } + break; + + case UIDeviceOrientationPortrait: + default: + { + orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile]; + startupImageTransform = CGAffineTransformIdentity; + center.y += MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; + } + break; + } + } else { // not iPad + orientedLaunchImageFile = launchImageFile; + } + + launchImage = [UIImage imageNamed:[[self class] resolveImageResource:orientedLaunchImageFile]]; + if (launchImage == nil) { + NSLog(@"WARNING: Splash-screen image '%@' was not found. Orientation: %d, iPad: %d", orientedLaunchImageFile, deviceOrientation, CDV_IsIPad()); + } + + self.imageView = [[UIImageView alloc] initWithImage:launchImage]; + self.imageView.tag = 1; + self.imageView.center = center; + + self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin); + [self.imageView setTransform:startupImageTransform]; + [self.view.superview addSubview:self.imageView]; + + /* + * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style. + * + * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge + * white = UIActivityIndicatorViewStyleWhite + * gray = UIActivityIndicatorViewStyleGray + * + */ + NSString* topActivityIndicator = [self.settings objectForKey:@"TopActivityIndicator"]; + UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; + + if ([topActivityIndicator isEqualToString:@"whiteLarge"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge; + } else if ([topActivityIndicator isEqualToString:@"white"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite; + } else if ([topActivityIndicator isEqualToString:@"gray"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; + } + + self.activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; + self.activityView.tag = 2; + + id showSplashScreenSpinnerValue = [self.settings objectForKey:@"ShowSplashScreenSpinner"]; + // backwards compatibility - if key is missing, default to true + if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { + [self.view.superview addSubview:self.activityView]; + } + + self.activityView.center = self.view.center; + [self.activityView startAnimating]; + + [self.view.superview layoutSubviews]; +} + +BOOL gSplashScreenShown = NO; +- (void)receivedOrientationChange +{ + if (self.imageView == nil) { + gSplashScreenShown = YES; + if (self.useSplashScreen) { + [self showSplashScreen]; + } + } +} + +#pragma mark CordovaCommands + +- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className +{ + if ([plugin respondsToSelector:@selector(setViewController:)]) { + [plugin setViewController:self]; + } + + if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) { + [plugin setCommandDelegate:_commandDelegate]; + } + + [self.pluginObjects setObject:plugin forKey:className]; +} + +/** + Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned. + */ +- (id)getCommandInstance:(NSString*)pluginName +{ + // first, we try to find the pluginName in the pluginsMap + // (acts as a whitelist as well) if it does not exist, we return nil + // NOTE: plugin names are matched as lowercase to avoid problems - however, a + // possible issue is there can be duplicates possible if you had: + // "org.apache.cordova.Foo" and "org.apache.cordova.foo" - only the lower-cased entry will match + NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]]; + + if (className == nil) { + return nil; + } + + id obj = [self.pluginObjects objectForKey:className]; + if (!obj) { + // attempt to load the settings for this command class + NSDictionary* classSettings = [self.settings objectForKey:className]; + + if (classSettings) { + obj = [[NSClassFromString (className)alloc] initWithWebView:webView settings:classSettings]; + } else { + obj = [[NSClassFromString (className)alloc] initWithWebView:webView]; + } + + if ((obj != nil) && [obj isKindOfClass:[CDVPlugin class]]) { + [self registerPlugin:obj withClassName:className]; + } else { + NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName); + } + } + return obj; +} + +#pragma mark - + +- (NSString*)appURLScheme +{ + NSString* URLScheme = nil; + + NSArray* URLTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleURLTypes"]; + + if (URLTypes != nil) { + NSDictionary* dict = [URLTypes objectAtIndex:0]; + if (dict != nil) { + NSArray* URLSchemes = [dict objectForKey:@"CFBundleURLSchemes"]; + if (URLSchemes != nil) { + URLScheme = [URLSchemes objectAtIndex:0]; + } + } + } + + return URLScheme; +} + +/** + Returns the contents of the named plist bundle, loaded as a dictionary object + */ ++ (NSDictionary*)getBundlePlist:(NSString*)plistName +{ + NSString* errorDesc = nil; + NSPropertyListFormat format; + NSString* plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"]; + NSData* plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath]; + NSDictionary* temp = (NSDictionary*)[NSPropertyListSerialization + propertyListFromData:plistXML + mutabilityOption:NSPropertyListMutableContainersAndLeaves + format:&format errorDescription:&errorDesc]; + + return temp; +} + +#pragma mark - +#pragma mark UIApplicationDelegate impl + +/* + This method lets your application know that it is about to be terminated and purged from memory entirely + */ +- (void)onAppWillTerminate:(NSNotification*)notification +{ + // empty the tmp directory + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing err = nil; + + // clear contents of NSTemporaryDirectory + NSString* tempDirectoryPath = NSTemporaryDirectory(); + NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath]; + NSString* fileName = nil; + BOOL result; + + while ((fileName = [directoryEnumerator nextObject])) { + NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName]; + result = [fileMgr removeItemAtPath:filePath error:&err]; + if (!result && err) { + NSLog(@"Failed to delete: %@ (error: %@)", filePath, err); + } + } +} + +/* + This method is called to let your application know that it is about to move from the active to inactive state. + You should use this method to pause ongoing tasks, disable timer, ... + */ +- (void)onAppWillResignActive:(NSNotification*)notification +{ + // NSLog(@"%@",@"applicationWillResignActive"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resign');" scheduledOnRunLoop:NO]; +} + +/* + In iOS 4.0 and later, this method is called as part of the transition from the background to the inactive state. + You can use this method to undo many of the changes you made to your application upon entering the background. + invariably followed by applicationDidBecomeActive + */ +- (void)onAppWillEnterForeground:(NSNotification*)notification +{ + // NSLog(@"%@",@"applicationWillEnterForeground"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resume');"]; +} + +// This method is called to let your application know that it moved from the inactive to active state. +- (void)onAppDidBecomeActive:(NSNotification*)notification +{ + // NSLog(@"%@",@"applicationDidBecomeActive"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('active');"]; +} + +/* + In iOS 4.0 and later, this method is called instead of the applicationWillTerminate: method + when the user quits an application that supports background execution. + */ +- (void)onAppDidEnterBackground:(NSNotification*)notification +{ + // NSLog(@"%@",@"applicationDidEnterBackground"); + [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('pause', null, true);" scheduledOnRunLoop:NO]; +} + +- (void)onAppLocaleDidChange:(NSNotification*)notification +{ + gOriginalUserAgent = nil; +} + +// /////////////////////// + +- (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]; + + self.webView.delegate = nil; + self.webView = nil; + [_commandQueue dispose]; + [[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVWhitelist.h b/iPhone/CordovaLib/Classes/CDVWhitelist.h new file mode 100755 index 0000000..3741e94 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVWhitelist.h @@ -0,0 +1,36 @@ +/* + 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> + +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; +- (NSString*)errorStringForURL:(NSURL*)url; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVWhitelist.m b/iPhone/CordovaLib/Classes/CDVWhitelist.m new file mode 100755 index 0000000..77e20ac --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVWhitelist.m @@ -0,0 +1,192 @@ +/* + 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 "CDVWhitelist.h" + +NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejection: url='%@'"; + +@interface CDVWhitelist () + +@property (nonatomic, readwrite, strong) NSArray* whitelist; +@property (nonatomic, readwrite, strong) NSArray* expandedWhitelist; +@property (nonatomic, readwrite, assign) BOOL allowAll; + +- (void)processWhitelist; + +@end + +@implementation CDVWhitelist + +@synthesize whitelist, expandedWhitelist, allowAll, whitelistRejectionFormatString; + +- (id)initWithArray:(NSArray*)array +{ + self = [super init]; + if (self) { + self.whitelist = array; + self.expandedWhitelist = nil; + self.allowAll = NO; + self.whitelistRejectionFormatString = kCDVDefaultWhitelistRejectionString; + [self processWhitelist]; + } + + return self; +} + +- (BOOL)isIPv4Address:(NSString*)externalHost +{ + // an IPv4 address has 4 octets b.b.b.b where b is a number between 0 and 255. + // for our purposes, b can also be the wildcard character '*' + + // we could use a regex to solve this problem but then I would have two problems + // anyways, this is much clearer and maintainable + NSArray* octets = [externalHost componentsSeparatedByString:@"."]; + NSUInteger num_octets = [octets count]; + + // quick check + if (num_octets != 4) { + return NO; + } + + // restrict number parsing to 0-255 + NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; + [numberFormatter setMinimum:[NSNumber numberWithUnsignedInteger:0]]; + [numberFormatter setMaximum:[NSNumber numberWithUnsignedInteger:255]]; + + // iterate through each octet, and test for a number between 0-255 or if it equals '*' + for (NSUInteger i = 0; i < num_octets; ++i) { + NSString* octet = [octets objectAtIndex:i]; + + if ([octet isEqualToString:@"*"]) { // passes - check next octet + continue; + } else if ([numberFormatter numberFromString:octet] == nil) { // fails - not a number and not within our range, return + return NO; + } + } + + return YES; +} + +- (NSString*)extractHostFromUrlString:(NSString*)url +{ + NSURL* aUrl = [NSURL URLWithString:url]; + + if ((aUrl != nil) && ([aUrl scheme] != nil)) { // found scheme + return [aUrl host]; + } else { + return url; + } +} + +- (void)processWhitelist +{ + if (self.whitelist == nil) { + NSLog(@"ERROR: CDVWhitelist was not initialized properly, all urls will be disallowed."); + return; + } + + NSMutableArray* expanded = [NSMutableArray arrayWithCapacity:[self.whitelist count]]; + + // iterate through settings ExternalHosts, check for equality + NSEnumerator* enumerator = [self.whitelist objectEnumerator]; + id externalHost = nil; + + // 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]; + + // check for single wildcard '*', if found set allowAll to YES + if ([regex isEqualToString:@"*"]) { + self.allowAll = YES; + self.expandedWhitelist = [NSArray arrayWithObject:regex]; + break; + } + + // starts with wildcard match - we make the first '.' optional (so '*.org.apache.cordova' will match 'org.apache.cordova') + NSString* prefix = @"*."; + if ([regex 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 + } + + // ends with wildcard match for TLD + if (!is_ip && [regex hasSuffix:@".*"]) { + // replace * with tld_match + regex = [regex stringByReplacingCharactersInRange:NSMakeRange([regex length] - 1, 1) withString:tld_match]; + } + // escape periods - since '.' means any character in regex + regex = [regex 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:@".*"]; + + [expanded addObject:regex]; + } + + self.expandedWhitelist = expanded; +} + +- (BOOL)schemeIsAllowed:(NSString*)scheme +{ + return [scheme isEqualToString:@"http"] || + [scheme isEqualToString:@"https"] || + [scheme isEqualToString:@"ftp"] || + [scheme isEqualToString:@"ftps"]; +} + +- (BOOL)URLIsAllowed:(NSURL*)url +{ + if (self.expandedWhitelist == nil) { + return NO; + } + + if (self.allowAll) { + return YES; + } + + // iterate through settings ExternalHosts, check for equality + NSEnumerator* enumerator = [self.expandedWhitelist objectEnumerator]; + id regex = nil; + NSString* urlHost = [url host]; + + // if the url host IS found in the whitelist, load it in the app (however UIWebViewNavigationTypeOther kicks it out to Safari) + // if the url host IS NOT found in the whitelist, we do nothing + while (regex = [enumerator nextObject]) { + NSPredicate* regex_test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex]; + + if ([regex_test evaluateWithObject:urlHost] == YES) { + // if it matches at least one rule, return + return YES; + } + } + + NSLog(@"%@", [self errorStringForURL:url]); + // if we got here, the url host is not in the white-list, do nothing + return NO; +} + +- (NSString*)errorStringForURL:(NSURL*)url +{ + return [NSString stringWithFormat:self.whitelistRejectionFormatString, [url absoluteString]]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/JSON/JSONKit.h b/iPhone/CordovaLib/Classes/JSON/JSONKit.h new file mode 100755 index 0000000..2b245cd --- /dev/null +++ b/iPhone/CordovaLib/Classes/JSON/JSONKit.h @@ -0,0 +1,251 @@ +// +// JSONKit.h +// http://github.com/johnezang/JSONKit +// Dual licensed under either the terms of the BSD License, or alternatively +// under the terms of the Apache License, Version 2.0, as specified below. +// + +/* + Copyright (c) 2011, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + Copyright 2011 John Engelhart + + Licensed 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 <stddef.h> +#include <stdint.h> +#include <limits.h> +#include <TargetConditionals.h> +#include <AvailabilityMacros.h> + +#ifdef __OBJC__ +#import <Foundation/NSArray.h> +#import <Foundation/NSData.h> +#import <Foundation/NSDictionary.h> +#import <Foundation/NSError.h> +#import <Foundation/NSObjCRuntime.h> +#import <Foundation/NSString.h> +#endif // __OBJC__ + +#ifdef __cplusplus +extern "C" { +#endif + + +// For Mac OS X < 10.5. +#ifndef NSINTEGER_DEFINED +#define NSINTEGER_DEFINED +#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) +typedef long NSInteger; +typedef unsigned long NSUInteger; +#define NSIntegerMin LONG_MIN +#define NSIntegerMax LONG_MAX +#define NSUIntegerMax ULONG_MAX +#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) +typedef int NSInteger; +typedef unsigned int NSUInteger; +#define NSIntegerMin INT_MIN +#define NSIntegerMax INT_MAX +#define NSUIntegerMax UINT_MAX +#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) +#endif // NSINTEGER_DEFINED + + +#ifndef _CDVJSONKIT_H_ +#define _CDVJSONKIT_H_ + +#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465) +#define CDVJK_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#else +#define CDVJK_DEPRECATED_ATTRIBUTE +#endif + +#define CDVJSONKIT_VERSION_MAJOR 1 +#define CDVJSONKIT_VERSION_MINOR 4 + +typedef NSUInteger CDVJKFlags; + +/* + CDVJKParseOptionComments : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON. + CDVJKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines. + CDVJKParseOptionLooseUnicode : Normally the decoder will stop with an error at any malformed Unicode. + This option allows JSON with malformed Unicode to be parsed without reporting an error. + Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER". + */ + +enum { + CDVJKParseOptionNone = 0, + CDVJKParseOptionStrict = 0, + CDVJKParseOptionComments = (1 << 0), + CDVJKParseOptionUnicodeNewlines = (1 << 1), + CDVJKParseOptionLooseUnicode = (1 << 2), + CDVJKParseOptionPermitTextAfterValidJSON = (1 << 3), + CDVJKParseOptionValidFlags = (CDVJKParseOptionComments | CDVJKParseOptionUnicodeNewlines | CDVJKParseOptionLooseUnicode | CDVJKParseOptionPermitTextAfterValidJSON), +}; +typedef CDVJKFlags CDVJKParseOptionFlags; + +enum { + CDVJKSerializeOptionNone = 0, + CDVJKSerializeOptionPretty = (1 << 0), + CDVJKSerializeOptionEscapeUnicode = (1 << 1), + CDVJKSerializeOptionEscapeForwardSlashes = (1 << 4), + CDVJKSerializeOptionValidFlags = (CDVJKSerializeOptionPretty | CDVJKSerializeOptionEscapeUnicode | CDVJKSerializeOptionEscapeForwardSlashes), +}; +typedef CDVJKFlags CDVJKSerializeOptionFlags; + +#ifdef __OBJC__ + +typedef struct CDVJKParseState CDVJKParseState; // Opaque internal, private type. + +// As a general rule of thumb, if you use a method that doesn't accept a CDVJKParseOptionFlags argument, it defaults to CDVJKParseOptionStrict + +@interface CDVJSONDecoder : NSObject { + CDVJKParseState *parseState; +} ++ (id)decoder; ++ (id)decoderWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (id)initWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (void)clearCache; + +// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead. +// The NSData MUST be UTF8 encoded JSON. +- (id)parseJSONData:(NSData *)jsonData CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead. +- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead. + +// Methods that return immutable collection objects. +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; +// The NSData MUST be UTF8 encoded JSON. +- (id)objectWithData:(NSData *)jsonData; +- (id)objectWithData:(NSData *)jsonData error:(NSError **)error; + +// Methods that return mutable collection objects. +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; +// The NSData MUST be UTF8 encoded JSON. +- (id)mutableObjectWithData:(NSData *)jsonData; +- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error; + +@end + +//////////// +#pragma mark Deserializing methods +//////////// + +@interface NSString (CDVJSONKitDeserializing) +- (id)cdvjk_objectFromJSONString; +- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; +- (id)cdvjk_mutableObjectFromJSONString; +- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; +@end + +@interface NSData (CDVJSONKitDeserializing) +// The NSData MUST be UTF8 encoded JSON. +- (id)cdvjk_objectFromJSONData; +- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; +- (id)cdvjk_mutableObjectFromJSONData; +- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; +- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; +@end + +//////////// +#pragma mark Serializing methods +//////////// + +@interface NSString (CDVJSONKitSerializing) +// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString). +// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes: +// includeQuotes:YES `a "test"...` -> `"a \"test\"..."` +// includeQuotes:NO `a "test"...` -> `a \"test\"...` +- (NSData *)cdvjk_JSONData; // Invokes JSONDataWithOptions:CDVJKSerializeOptionNone includeQuotes:YES +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; +- (NSString *)cdvjk_JSONString; // Invokes JSONStringWithOptions:CDVJKSerializeOptionNone includeQuotes:YES +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; +@end + +@interface NSArray (CDVJSONKitSerializing) +- (NSData *)cdvjk_JSONData; +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (NSString *)cdvjk_JSONString; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +@end + +@interface NSDictionary (CDVJSONKitSerializing) +- (NSData *)cdvjk_JSONData; +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (NSString *)cdvjk_JSONString; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +@end + +#ifdef __BLOCKS__ + +@interface NSArray (CDVJSONKitSerializingBlockAdditions) +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +@end + +@interface NSDictionary (CDVJSONKitSerializingBlockAdditions) +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +@end + +#endif + + +#endif // __OBJC__ + +#endif // _CDVJSONKIT_H_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/iPhone/CordovaLib/Classes/JSON/JSONKit.m b/iPhone/CordovaLib/Classes/JSON/JSONKit.m new file mode 100755 index 0000000..15a7171 --- /dev/null +++ b/iPhone/CordovaLib/Classes/JSON/JSONKit.m @@ -0,0 +1,3061 @@ +// +// JSONKit.m +// http://github.com/johnezang/JSONKit +// Dual licensed under either the terms of the BSD License, or alternatively +// under the terms of the Apache License, Version 2.0, as specified below. +// + +/* + Copyright (c) 2011, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + Copyright 2011 John Engelhart + + Licensed 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. +*/ + + +/* + Acknowledgments: + + The bulk of the UTF8 / UTF32 conversion and verification comes + from ConvertUTF.[hc]. It has been modified from the original sources. + + The original sources were obtained from http://www.unicode.org/. + However, the web site no longer seems to host the files. Instead, + the Unicode FAQ http://www.unicode.org/faq//utf_bom.html#gen4 + points to International Components for Unicode (ICU) + http://site.icu-project.org/ as an example of how to write a UTF + converter. + + The decision to use the ConvertUTF.[ch] code was made to leverage + "proven" code. Hopefully the local modifications are bug free. + + The code in isValidCodePoint() is derived from the ICU code in + utf.h for the macros U_IS_UNICODE_NONCHAR and U_IS_UNICODE_CHAR. + + From the original ConvertUTF.[ch]: + + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <sys/errno.h> +#include <math.h> +#include <limits.h> +#include <objc/runtime.h> + +#import "JSONKit.h" + +//#include <CoreFoundation/CoreFoundation.h> +#include <CoreFoundation/CFString.h> +#include <CoreFoundation/CFArray.h> +#include <CoreFoundation/CFDictionary.h> +#include <CoreFoundation/CFNumber.h> + +//#import <Foundation/Foundation.h> +#import <Foundation/NSArray.h> +#import <Foundation/NSAutoreleasePool.h> +#import <Foundation/NSData.h> +#import <Foundation/NSDictionary.h> +#import <Foundation/NSException.h> +#import <Foundation/NSNull.h> +#import <Foundation/NSObjCRuntime.h> + +#ifndef __cdv_has_feature +#define __cdv_has_feature(x) 0 +#endif + +#ifdef CDVJK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS +#warning As of CDVJSONKit v1.4, CDVJK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS is no longer required. It is no longer a valid option. +#endif + +#ifdef __OBJC_GC__ +#error CDVJSONKit does not support Objective-C Garbage Collection +#endif + +#if __has_feature(objc_arc) +#error CDVJSONKit does not support Objective-C Automatic Reference Counting (ARC) +#endif + +// The following checks are really nothing more than sanity checks. +// CDVJSONKit technically has a few problems from a "strictly C99 conforming" standpoint, though they are of the pedantic nitpicking variety. +// In practice, though, for the compilers and architectures we can reasonably expect this code to be compiled for, these pedantic nitpicks aren't really a problem. +// Since we're limited as to what we can do with pre-processor #if checks, these checks are not nearly as through as they should be. + +#if (UINT_MAX != 0xffffffffU) || (INT_MIN != (-0x7fffffff-1)) || (ULLONG_MAX != 0xffffffffffffffffULL) || (LLONG_MIN != (-0x7fffffffffffffffLL-1LL)) +#error CDVJSONKit requires the C 'int' and 'long long' types to be 32 and 64 bits respectively. +#endif + +#if !defined(__LP64__) && ((UINT_MAX != ULONG_MAX) || (INT_MAX != LONG_MAX) || (INT_MIN != LONG_MIN) || (WORD_BIT != LONG_BIT)) +#error CDVJSONKit requires the C 'int' and 'long' types to be the same on 32-bit architectures. +#endif + +// Cocoa / Foundation uses NS*Integer as the type for a lot of arguments. We make sure that NS*Integer is something we are expecting and is reasonably compatible with size_t / ssize_t + +#if (NSUIntegerMax != ULONG_MAX) || (NSIntegerMax != LONG_MAX) || (NSIntegerMin != LONG_MIN) +#error CDVJSONKit requires NSInteger and NSUInteger to be the same size as the C 'long' type. +#endif + +#if (NSUIntegerMax != SIZE_MAX) || (NSIntegerMax != SSIZE_MAX) +#error CDVJSONKit requires NSInteger and NSUInteger to be the same size as the C 'size_t' type. +#endif + + +// For DJB hash. +#define CDVJK_HASH_INIT (1402737925UL) + +// Use __builtin_clz() instead of trailingBytesForUTF8[] table lookup. +#define CDVJK_FAST_TRAILING_BYTES + +// CDVJK_CACHE_SLOTS must be a power of 2. Default size is 1024 slots. +#define CDVJK_CACHE_SLOTS_BITS (10) +#define CDVJK_CACHE_SLOTS (1UL << CDVJK_CACHE_SLOTS_BITS) +// CDVJK_CACHE_PROBES is the number of probe attempts. +#define CDVJK_CACHE_PROBES (4UL) +// CDVJK_INIT_CACHE_AGE must be (1 << AGE) - 1 +#define CDVJK_INIT_CACHE_AGE (0) + +// CDVJK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes) +#define CDVJK_TOKENBUFFER_SIZE (1024UL * 2UL) + +// CDVJK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transferred to a NSArray / NSDictionary. +#define CDVJK_STACK_OBJS (1024UL * 1UL) + +#define CDVJK_JSONBUFFER_SIZE (1024UL * 4UL) +#define CDVJK_UTF8BUFFER_SIZE (1024UL * 16UL) + +#define CDVJK_ENCODE_CACHE_SLOTS (1024UL) + + +#if defined (__GNUC__) && (__GNUC__ >= 4) +#define CDVJK_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__)) +#define CDVJK_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect)) +#define CDVJK_EXPECT_T(cond) CDVJK_EXPECTED(cond, 1U) +#define CDVJK_EXPECT_F(cond) CDVJK_EXPECTED(cond, 0U) +#define CDVJK_PREFETCH(ptr) __builtin_prefetch(ptr) +#else // defined (__GNUC__) && (__GNUC__ >= 4) +#define CDVJK_ATTRIBUTES(attr, ...) +#define CDVJK_EXPECTED(cond, expect) (cond) +#define CDVJK_EXPECT_T(cond) (cond) +#define CDVJK_EXPECT_F(cond) (cond) +#define CDVJK_PREFETCH(ptr) +#endif // defined (__GNUC__) && (__GNUC__ >= 4) + +#define CDVJK_STATIC_INLINE static __inline__ CDVJK_ATTRIBUTES(always_inline) +#define CDVJK_ALIGNED(arg) CDVJK_ATTRIBUTES(aligned(arg)) +#define CDVJK_UNUSED_ARG CDVJK_ATTRIBUTES(unused) +#define CDVJK_WARN_UNUSED CDVJK_ATTRIBUTES(warn_unused_result) +#define CDVJK_WARN_UNUSED_CONST CDVJK_ATTRIBUTES(warn_unused_result, const) +#define CDVJK_WARN_UNUSED_PURE CDVJK_ATTRIBUTES(warn_unused_result, pure) +#define CDVJK_WARN_UNUSED_SENTINEL CDVJK_ATTRIBUTES(warn_unused_result, sentinel) +#define CDVJK_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__)) +#define CDVJK_WARN_UNUSED_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__)) +#define CDVJK_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__)) +#define CDVJK_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__)) + +#if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define CDVJK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as)) +#else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define CDVJK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) +#endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) + + +@class CDVJKArray, CDVJKDictionaryEnumerator, CDVJKDictionary; + +enum { + CDVJSONNumberStateStart = 0, + CDVJSONNumberStateFinished = 1, + CDVJSONNumberStateError = 2, + CDVJSONNumberStateWholeNumberStart = 3, + CDVJSONNumberStateWholeNumberMinus = 4, + CDVJSONNumberStateWholeNumberZero = 5, + CDVJSONNumberStateWholeNumber = 6, + CDVJSONNumberStatePeriod = 7, + CDVJSONNumberStateFractionalNumberStart = 8, + CDVJSONNumberStateFractionalNumber = 9, + CDVJSONNumberStateExponentStart = 10, + CDVJSONNumberStateExponentPlusMinus = 11, + CDVJSONNumberStateExponent = 12, +}; + +enum { + CDVJSONStringStateStart = 0, + CDVJSONStringStateParsing = 1, + CDVJSONStringStateFinished = 2, + CDVJSONStringStateError = 3, + CDVJSONStringStateEscape = 4, + CDVJSONStringStateEscapedUnicode1 = 5, + CDVJSONStringStateEscapedUnicode2 = 6, + CDVJSONStringStateEscapedUnicode3 = 7, + CDVJSONStringStateEscapedUnicode4 = 8, + CDVJSONStringStateEscapedUnicodeSurrogate1 = 9, + CDVJSONStringStateEscapedUnicodeSurrogate2 = 10, + CDVJSONStringStateEscapedUnicodeSurrogate3 = 11, + CDVJSONStringStateEscapedUnicodeSurrogate4 = 12, + CDVJSONStringStateEscapedNeedEscapeForSurrogate = 13, + CDVJSONStringStateEscapedNeedEscapedUForSurrogate = 14, +}; + +enum { + CDVJKParseAcceptValue = (1 << 0), + CDVJKParseAcceptComma = (1 << 1), + CDVJKParseAcceptEnd = (1 << 2), + CDVJKParseAcceptValueOrEnd = (CDVJKParseAcceptValue | CDVJKParseAcceptEnd), + CDVJKParseAcceptCommaOrEnd = (CDVJKParseAcceptComma | CDVJKParseAcceptEnd), +}; + +enum { + CDVJKClassUnknown = 0, + CDVJKClassString = 1, + CDVJKClassNumber = 2, + CDVJKClassArray = 3, + CDVJKClassDictionary = 4, + CDVJKClassNull = 5, +}; + +enum { + CDVJKManagedBufferOnStack = 1, + CDVJKManagedBufferOnHeap = 2, + CDVJKManagedBufferLocationMask = (0x3), + CDVJKManagedBufferLocationShift = (0), + + CDVJKManagedBufferMustFree = (1 << 2), +}; +typedef CDVJKFlags CDVJKManagedBufferFlags; + +enum { + CDVJKObjectStackOnStack = 1, + CDVJKObjectStackOnHeap = 2, + CDVJKObjectStackLocationMask = (0x3), + CDVJKObjectStackLocationShift = (0), + + CDVJKObjectStackMustFree = (1 << 2), +}; +typedef CDVJKFlags CDVJKObjectStackFlags; + +enum { + CDVJKTokenTypeInvalid = 0, + CDVJKTokenTypeNumber = 1, + CDVJKTokenTypeString = 2, + CDVJKTokenTypeObjectBegin = 3, + CDVJKTokenTypeObjectEnd = 4, + CDVJKTokenTypeArrayBegin = 5, + CDVJKTokenTypeArrayEnd = 6, + CDVJKTokenTypeSeparator = 7, + CDVJKTokenTypeComma = 8, + CDVJKTokenTypeTrue = 9, + CDVJKTokenTypeFalse = 10, + CDVJKTokenTypeNull = 11, + CDVJKTokenTypeWhiteSpace = 12, +}; +typedef NSUInteger CDVJKTokenType; + +// These are prime numbers to assist with hash slot probing. +enum { + CDVJKValueTypeNone = 0, + CDVJKValueTypeString = 5, + CDVJKValueTypeLongLong = 7, + CDVJKValueTypeUnsignedLongLong = 11, + CDVJKValueTypeDouble = 13, +}; +typedef NSUInteger CDVJKValueType; + +enum { + CDVJKEncodeOptionAsData = 1, + CDVJKEncodeOptionAsString = 2, + CDVJKEncodeOptionAsTypeMask = 0x7, + CDVJKEncodeOptionCollectionObj = (1 << 3), + CDVJKEncodeOptionStringObj = (1 << 4), + CDVJKEncodeOptionStringObjTrimQuotes = (1 << 5), + +}; +typedef NSUInteger CDVJKEncodeOptionType; + +typedef NSUInteger CDVJKHash; + +typedef struct CDVJKTokenCacheItem CDVJKTokenCacheItem; +typedef struct CDVJKTokenCache CDVJKTokenCache; +typedef struct CDVJKTokenValue CDVJKTokenValue; +typedef struct CDVJKParseToken CDVJKParseToken; +typedef struct CDVJKPtrRange CDVJKPtrRange; +typedef struct CDVJKObjectStack CDVJKObjectStack; +typedef struct CDVJKBuffer CDVJKBuffer; +typedef struct CDVJKConstBuffer CDVJKConstBuffer; +typedef struct CDVJKConstPtrRange CDVJKConstPtrRange; +typedef struct CDVJKRange CDVJKRange; +typedef struct CDVJKManagedBuffer CDVJKManagedBuffer; +typedef struct CDVJKFastClassLookup CDVJKFastClassLookup; +typedef struct CDVJKEncodeCache CDVJKEncodeCache; +typedef struct CDVJKEncodeState CDVJKEncodeState; +typedef struct CDVJKObjCImpCache CDVJKObjCImpCache; +typedef struct CDVJKHashTableEntry CDVJKHashTableEntry; + +typedef id (*NSNumberAllocImp)(id receiver, SEL selector); +typedef id (*NSNumberInitWithUnsignedLongLongImp)(id receiver, SEL selector, unsigned long long value); +typedef id (*CDVJKClassFormatterIMP)(id receiver, SEL selector, id object); +#ifdef __BLOCKS__ +typedef id (^CDVJKClassFormatterBlock)(id formatObject); +#endif + + +struct CDVJKPtrRange { + unsigned char *ptr; + size_t length; +}; + +struct CDVJKConstPtrRange { + const unsigned char *ptr; + size_t length; +}; + +struct CDVJKRange { + size_t location, length; +}; + +struct CDVJKManagedBuffer { + CDVJKPtrRange bytes; + CDVJKManagedBufferFlags flags; + size_t roundSizeUpToMultipleOf; +}; + +struct CDVJKObjectStack { + void **objects, **keys; + CFHashCode *cfHashes; + size_t count, index, roundSizeUpToMultipleOf; + CDVJKObjectStackFlags flags; +}; + +struct CDVJKBuffer { + CDVJKPtrRange bytes; +}; + +struct CDVJKConstBuffer { + CDVJKConstPtrRange bytes; +}; + +struct CDVJKTokenValue { + CDVJKConstPtrRange ptrRange; + CDVJKValueType type; + CDVJKHash hash; + union { + long long longLongValue; + unsigned long long unsignedLongLongValue; + double doubleValue; + } number; + CDVJKTokenCacheItem *cacheItem; +}; + +struct CDVJKParseToken { + CDVJKConstPtrRange tokenPtrRange; + CDVJKTokenType type; + CDVJKTokenValue value; + CDVJKManagedBuffer tokenBuffer; +}; + +struct CDVJKTokenCacheItem { + void *object; + CDVJKHash hash; + CFHashCode cfHash; + size_t size; + unsigned char *bytes; + CDVJKValueType type; +}; + +struct CDVJKTokenCache { + CDVJKTokenCacheItem *items; + size_t count; + unsigned int prng_lfsr; + unsigned char age[CDVJK_CACHE_SLOTS]; +}; + +struct CDVJKObjCImpCache { + Class NSNumberClass; + NSNumberAllocImp NSNumberAlloc; + NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong; +}; + +struct CDVJKParseState { + CDVJKParseOptionFlags parseOptionFlags; + CDVJKConstBuffer stringBuffer; + size_t atIndex, lineNumber, lineStartIndex; + size_t prev_atIndex, prev_lineNumber, prev_lineStartIndex; + CDVJKParseToken token; + CDVJKObjectStack objectStack; + CDVJKTokenCache cache; + CDVJKObjCImpCache objCImpCache; + NSError *error; + int errorIsPrev; + BOOL mutableCollections; +}; + +struct CDVJKFastClassLookup { + void *stringClass; + void *numberClass; + void *arrayClass; + void *dictionaryClass; + void *nullClass; +}; + +struct CDVJKEncodeCache { + id object; + size_t offset; + size_t length; +}; + +struct CDVJKEncodeState { + CDVJKManagedBuffer utf8ConversionBuffer; + CDVJKManagedBuffer stringBuffer; + size_t atIndex; + CDVJKFastClassLookup fastClassLookup; + CDVJKEncodeCache cache[CDVJK_ENCODE_CACHE_SLOTS]; + CDVJKSerializeOptionFlags serializeOptionFlags; + CDVJKEncodeOptionType encodeOption; + size_t depth; + NSError *error; + id classFormatterDelegate; + SEL classFormatterSelector; + CDVJKClassFormatterIMP classFormatterIMP; +#ifdef __BLOCKS__ + CDVJKClassFormatterBlock classFormatterBlock; +#endif +}; + +// This is a CDVJSONKit private class. +@interface CDVJKSerializer : NSObject { + CDVJKEncodeState *encodeState; +} + +#ifdef __BLOCKS__ +#define CDVJKSERIALIZER_BLOCKS_PROTO id(^)(id object) +#else +#define CDVJKSERIALIZER_BLOCKS_PROTO id +#endif + ++ (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (void)releaseState; + +@end + +struct CDVJKHashTableEntry { + NSUInteger keyHash; + id key, object; +}; + + +typedef uint32_t UTF32; /* at least 32 bits */ +typedef uint16_t UTF16; /* at least 16 bits */ +typedef uint8_t UTF8; /* typically 8 bits */ + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} CDV_ConversionResult; + +#define CDV_UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define CDV_UNI_MAX_BMP (UTF32)0x0000FFFF +#define CDV_UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define CDV_UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define CDV_UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF +#define CDV_UNI_SUR_HIGH_START (UTF32)0xD800 +#define CDV_UNI_SUR_HIGH_END (UTF32)0xDBFF +#define CDV_UNI_SUR_LOW_START (UTF32)0xDC00 +#define CDV_UNI_SUR_LOW_END (UTF32)0xDFFF + + +#if !defined(CDVJK_FAST_TRAILING_BYTES) +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; +#endif + +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +#define CDVJK_AT_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->atIndex])) +#define CDVJK_END_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->stringBuffer.bytes.length])) + + +static CDVJKArray *_CDVJKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection); +static void _CDVJKArrayInsertObjectAtIndex(CDVJKArray *array, id newObject, NSUInteger objectIndex); +static void _CDVJKArrayReplaceObjectAtIndexWithObject(CDVJKArray *array, NSUInteger objectIndex, id newObject); +static void _CDVJKArrayRemoveObjectAtIndex(CDVJKArray *array, NSUInteger objectIndex); + + +static NSUInteger _CDVJKDictionaryCapacityForCount(NSUInteger count); +static CDVJKDictionary *_CDVJKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection); +static CDVJKHashTableEntry *_CDVJKDictionaryHashEntry(CDVJKDictionary *dictionary); +static NSUInteger _CDVJKDictionaryCapacity(CDVJKDictionary *dictionary); +static void _CDVJKDictionaryResizeIfNeccessary(CDVJKDictionary *dictionary); +static void _CDVJKDictionaryRemoveObjectWithEntry(CDVJKDictionary *dictionary, CDVJKHashTableEntry *entry); +static void _CDVJKDictionaryAddObject(CDVJKDictionary *dictionary, NSUInteger keyHash, id key, id object); +static CDVJKHashTableEntry *_CDVJKDictionaryHashTableEntryForKey(CDVJKDictionary *dictionary, id aKey); + + +static void _CDVJSONDecoderCleanup(CDVJSONDecoder *decoder); + +static id _CDVNSStringObjectFromJSONString(NSString *jsonString, CDVJKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection); + + +static void cdvjk_managedBuffer_release(CDVJKManagedBuffer *managedBuffer); +static void cdvjk_managedBuffer_setToStackBuffer(CDVJKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length); +static unsigned char *cdvjk_managedBuffer_resize(CDVJKManagedBuffer *managedBuffer, size_t newSize); +static void cdvjk_objectStack_release(CDVJKObjectStack *objectStack); +static void cdvjk_objectStack_setToStackBuffer(CDVJKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count); +static int cdvjk_objectStack_resize(CDVJKObjectStack *objectStack, size_t newCount); + +static void cdvjk_error(CDVJKParseState *parseState, NSString *format, ...); +static int cdvjk_parse_string(CDVJKParseState *parseState); +static int cdvjk_parse_number(CDVJKParseState *parseState); +static size_t cdvjk_parse_is_newline(CDVJKParseState *parseState, const unsigned char *atCharacterPtr); +CDVJK_STATIC_INLINE int cdvjk_parse_skip_newline(CDVJKParseState *parseState); +CDVJK_STATIC_INLINE void cdvjk_parse_skip_whitespace(CDVJKParseState *parseState); +static int cdvjk_parse_next_token(CDVJKParseState *parseState); +static void cdvjk_error_parse_accept_or3(CDVJKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String); +static void *cdvjk_create_dictionary(CDVJKParseState *parseState, size_t startingObjectIndex); +static void *cdvjk_parse_dictionary(CDVJKParseState *parseState); +static void *cdvjk_parse_array(CDVJKParseState *parseState); +static void *cdvjk_object_for_token(CDVJKParseState *parseState); +static void *cdvjk_cachedObjects(CDVJKParseState *parseState); +CDVJK_STATIC_INLINE void cdvjk_cache_age(CDVJKParseState *parseState); +CDVJK_STATIC_INLINE void cdvjk_set_parsed_token(CDVJKParseState *parseState, const unsigned char *ptr, size_t length, CDVJKTokenType type, size_t advanceBy); + + +static void cdvjk_encode_error(CDVJKEncodeState *encodeState, NSString *format, ...); +static int cdvjk_encode_printf(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...); +static int cdvjk_encode_write(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format); +static int cdvjk_encode_writePrettyPrintWhiteSpace(CDVJKEncodeState *encodeState); +static int cdvjk_encode_write1slow(CDVJKEncodeState *encodeState, ssize_t depthChange, const char *format); +static int cdvjk_encode_write1fast(CDVJKEncodeState *encodeState, ssize_t depthChange CDVJK_UNUSED_ARG, const char *format); +static int cdvjk_encode_writen(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length); +CDVJK_STATIC_INLINE CDVJKHash cdvjk_encode_object_hash(void *objectPtr); +CDVJK_STATIC_INLINE void cdvjk_encode_updateCache(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object); +static int cdvjk_encode_add_atom_to_buffer(CDVJKEncodeState *encodeState, void *objectPtr); + +#define cdvjk_encode_write1(es, dc, f) (CDVJK_EXPECT_F(_jk_encode_prettyPrint) ? cdvjk_encode_write1slow(es, dc, f) : cdvjk_encode_write1fast(es, dc, f)) + + +CDVJK_STATIC_INLINE size_t cdvjk_min(size_t a, size_t b); +CDVJK_STATIC_INLINE size_t cdvjk_max(size_t a, size_t b); +CDVJK_STATIC_INLINE CDVJKHash cdvcalculateHash(CDVJKHash currentHash, unsigned char c); + +// CDVJSONKit v1.4 used both a CDVJKArray : NSArray and CDVJKMutableArray : NSMutableArray, and the same for the dictionary collection type. +// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the +// mutable version, and keeps an ivar bit for whether or not that instance is mutable. This means that the immutable versions of the collection +// classes receive the mutating methods, but this is handled by having those methods throw an exception when the ivar bit is set to immutable. +// We adopt the same strategy here. It's both cleaner and gets rid of the method swizzling hackery used in CDVJSONKit v1.4. + + +// This is a workaround for issue #23 https://github.com/johnezang/JSONKit/pull/23 +// Basically, there seem to be a problem with using +load in static libraries on iOS. However, __attribute__ ((constructor)) does work correctly. +// Since we do not require anything "special" that +load provides, and we can accomplish the same thing using __attribute__ ((constructor)), the +load logic was moved here. + +static Class _CDVJKArrayClass = NULL; +static size_t _CDVJKArrayInstanceSize = 0UL; +static Class _CDVJKDictionaryClass = NULL; +static size_t _CDVJKDictionaryInstanceSize = 0UL; + +// For CDVJSONDecoder... +static Class _CDVjk_NSNumberClass = NULL; +static NSNumberAllocImp _CDVjk_NSNumberAllocImp = NULL; +static NSNumberInitWithUnsignedLongLongImp _CDVjk_NSNumberInitWithUnsignedLongLongImp = NULL; + +extern void cdvjk_collectionClassLoadTimeInitialization(void) __attribute__ ((constructor)); + +void cdvjk_collectionClassLoadTimeInitialization(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at load time initialization may be less than ideal. + + _CDVJKArrayClass = objc_getClass("CDVJKArray"); + _CDVJKArrayInstanceSize = cdvjk_max(16UL, class_getInstanceSize(_CDVJKArrayClass)); + + _CDVJKDictionaryClass = objc_getClass("CDVJKDictionary"); + _CDVJKDictionaryInstanceSize = cdvjk_max(16UL, class_getInstanceSize(_CDVJKDictionaryClass)); + + // For CDVJSONDecoder... + _CDVjk_NSNumberClass = [NSNumber class]; + _CDVjk_NSNumberAllocImp = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)]; + + // Hacktacular. Need to do it this way due to the nature of class clusters. + id temp_NSNumber = [NSNumber alloc]; + _CDVjk_NSNumberInitWithUnsignedLongLongImp = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)]; + [[temp_NSNumber init] release]; + temp_NSNumber = NULL; + + [pool release]; pool = NULL; +} + + +#pragma mark - +@interface CDVJKArray : NSMutableArray <NSCopying, NSMutableCopying, NSFastEnumeration> { + id *objects; + NSUInteger count, capacity, mutations; +} +@end + +@implementation CDVJKArray + ++ (id)allocWithZone:(NSZone *)zone +{ +#pragma unused(zone) + [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to CDVJSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; + return(NULL); +} + +static CDVJKArray *_CDVJKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection) { + NSCParameterAssert((objects != NULL) && (_CDVJKArrayClass != NULL) && (_CDVJKArrayInstanceSize > 0UL)); + CDVJKArray *array = NULL; + if(CDVJK_EXPECT_T((array = (CDVJKArray *)calloc(1UL, _CDVJKArrayInstanceSize)) != NULL)) { // Directly allocate the CDVJKArray instance via calloc. + array->isa = _CDVJKArrayClass; + if((array = [array init]) == NULL) { return(NULL); } + array->capacity = count; + array->count = count; + if(CDVJK_EXPECT_F((array->objects = (id *)malloc(sizeof(id) * array->capacity)) == NULL)) { [array autorelease]; return(NULL); } + memcpy(array->objects, objects, array->capacity * sizeof(id)); + array->mutations = (mutableCollection == NO) ? 0UL : 1UL; + } + return(array); +} + +// Note: The caller is responsible for -retaining the object that is to be added. +static void _CDVJKArrayInsertObjectAtIndex(CDVJKArray *array, id newObject, NSUInteger objectIndex) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex <= array->count) && (newObject != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (objectIndex <= array->count) && (newObject != NULL))) { [newObject autorelease]; return; } + if((array->count + 1UL) >= array->capacity) { + id *newObjects = NULL; + if((newObjects = (id *)realloc(array->objects, sizeof(id) * (array->capacity + 16UL))) == NULL) { [NSException raise:NSMallocException format:@"Unable to resize objects array."]; } + array->objects = newObjects; + array->capacity += 16UL; + memset(&array->objects[array->count], 0, sizeof(id) * (array->capacity - array->count)); + } + array->count++; + if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex + 1UL], &array->objects[objectIndex], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[objectIndex] = NULL; } + array->objects[objectIndex] = newObject; +} + +// Note: The caller is responsible for -retaining the object that is to be added. +static void _CDVJKArrayReplaceObjectAtIndexWithObject(CDVJKArray *array, NSUInteger objectIndex, id newObject) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL))) { [newObject autorelease]; return; } + CFRelease(array->objects[objectIndex]); + array->objects[objectIndex] = NULL; + array->objects[objectIndex] = newObject; +} + +static void _CDVJKArrayRemoveObjectAtIndex(CDVJKArray *array, NSUInteger objectIndex) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL))) { return; } + CFRelease(array->objects[objectIndex]); + array->objects[objectIndex] = NULL; + if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex], &array->objects[objectIndex + 1UL], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[array->count - 1UL] = NULL; } + array->count--; +} + +- (void)dealloc +{ + if(CDVJK_EXPECT_T(objects != NULL)) { + NSUInteger atObject = 0UL; + for(atObject = 0UL; atObject < count; atObject++) { if(CDVJK_EXPECT_T(objects[atObject] != NULL)) { CFRelease(objects[atObject]); objects[atObject] = NULL; } } + free(objects); objects = NULL; + } + + [super dealloc]; +} + +- (NSUInteger)count +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return(count); +} + +- (void)getObjects:(id *)objectsPtr range:(NSRange)range +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %u", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range)]; } + if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range), count]; } + if (objectsPtr != NULL) { + memcpy(objectsPtr, objects + range.location, range.length * sizeof(id)); + } +} + +- (id)objectAtIndex:(NSUInteger)objectIndex +{ + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL)); + return(objects[objectIndex]); +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (objects != NULL) && (count <= capacity)); + if(CDVJK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } + if(CDVJK_EXPECT_F(state->state >= count)) { return(0UL); } + + NSUInteger enumeratedCount = 0UL; + while(CDVJK_EXPECT_T(enumeratedCount < len) && CDVJK_EXPECT_T(state->state < count)) { NSParameterAssert(objects[state->state] != NULL); stackbuf[enumeratedCount++] = objects[state->state++]; } + + return(enumeratedCount); +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count + 1UL]; } +#ifdef __clang_analyzer__ + [anObject retain]; // Stupid clang analyzer... Issue #19. +#else + anObject = [anObject retain]; +#endif + _CDVJKArrayInsertObjectAtIndex(self, anObject, objectIndex); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)removeObjectAtIndex:(NSUInteger)objectIndex +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + _CDVJKArrayRemoveObjectAtIndex(self, objectIndex); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } +#ifdef __clang_analyzer__ + [anObject retain]; // Stupid clang analyzer... Issue #19. +#else + anObject = [anObject retain]; +#endif + _CDVJKArrayReplaceObjectAtIndexWithObject(self, objectIndex, anObject); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (id)copyWithZone:(NSZone *)zone +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return((mutations == 0UL) ? [self retain] : [(NSArray *)[NSArray allocWithZone:zone] initWithObjects:objects count:count]); +} + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return([(NSMutableArray *)[NSMutableArray allocWithZone:zone] initWithObjects:objects count:count]); +} + +@end + + +#pragma mark - +@interface CDVJKDictionaryEnumerator : NSEnumerator { + id collection; + NSUInteger nextObject; +} + +- (id)initWithJKDictionary:(CDVJKDictionary *)initDictionary; +- (NSArray *)allObjects; +- (id)nextObject; + +@end + +@implementation CDVJKDictionaryEnumerator + +- (id)initWithJKDictionary:(CDVJKDictionary *)initDictionary +{ + NSParameterAssert(initDictionary != NULL); + if((self = [super init]) == NULL) { return(NULL); } + if((collection = (id)CFRetain(initDictionary)) == NULL) { [self autorelease]; return(NULL); } + return(self); +} + +- (void)dealloc +{ + if(collection != NULL) { CFRelease(collection); collection = NULL; } + [super dealloc]; +} + +- (NSArray *)allObjects +{ + NSParameterAssert(collection != NULL); + NSUInteger count = [collection count], atObject = 0UL; + id objects[count]; + + while((objects[atObject] = [self nextObject]) != NULL) { NSParameterAssert(atObject < count); atObject++; } + + return([NSArray arrayWithObjects:objects count:atObject]); +} + +- (id)nextObject +{ + NSParameterAssert((collection != NULL) && (_CDVJKDictionaryHashEntry(collection) != NULL)); + CDVJKHashTableEntry *entry = _CDVJKDictionaryHashEntry(collection); + NSUInteger capacity = _CDVJKDictionaryCapacity(collection); + id returnObject = NULL; + + if(entry != NULL) { while((nextObject < capacity) && ((returnObject = entry[nextObject++].key) == NULL)) { /* ... */ } } + + return(returnObject); +} + +@end + +#pragma mark - +@interface CDVJKDictionary : NSMutableDictionary <NSCopying, NSMutableCopying, NSFastEnumeration> { + NSUInteger count, capacity, mutations; + CDVJKHashTableEntry *entry; +} +@end + +@implementation CDVJKDictionary + ++ (id)allocWithZone:(NSZone *)zone +{ +#pragma unused(zone) + [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to CDVJSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; + return(NULL); +} + +// These values are taken from Core Foundation CF-550 CFBasicHash.m. As a bonus, they align very well with our CDVJKHashTableEntry struct too. +static const NSUInteger cdvjk_dictionaryCapacities[] = { + 0UL, 3UL, 7UL, 13UL, 23UL, 41UL, 71UL, 127UL, 191UL, 251UL, 383UL, 631UL, 1087UL, 1723UL, + 2803UL, 4523UL, 7351UL, 11959UL, 19447UL, 31231UL, 50683UL, 81919UL, 132607UL, + 214519UL, 346607UL, 561109UL, 907759UL, 1468927UL, 2376191UL, 3845119UL, + 6221311UL, 10066421UL, 16287743UL, 26354171UL, 42641881UL, 68996069UL, + 111638519UL, 180634607UL, 292272623UL, 472907251UL +}; + +static NSUInteger _CDVJKDictionaryCapacityForCount(NSUInteger count) { + NSUInteger bottom = 0UL, top = sizeof(cdvjk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = lround(floor((count) * 1.33)); + while(top > bottom) { mid = (top + bottom) / 2UL; if(cdvjk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } } + return(cdvjk_dictionaryCapacities[bottom]); +} + +static void _CDVJKDictionaryResizeIfNeccessary(CDVJKDictionary *dictionary) { + NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); + + NSUInteger capacityForCount = 0UL; + if(dictionary->capacity < (capacityForCount = _CDVJKDictionaryCapacityForCount(dictionary->count + 1UL))) { // resize + NSUInteger oldCapacity = dictionary->capacity; +#ifndef NS_BLOCK_ASSERTIONS + NSUInteger oldCount = dictionary->count; +#endif + CDVJKHashTableEntry *oldEntry = dictionary->entry; + if(CDVJK_EXPECT_F((dictionary->entry = (CDVJKHashTableEntry *)calloc(1UL, sizeof(CDVJKHashTableEntry) * capacityForCount)) == NULL)) { [NSException raise:NSMallocException format:@"Unable to allocate memory for hash table."]; } + dictionary->capacity = capacityForCount; + dictionary->count = 0UL; + + NSUInteger idx = 0UL; + for(idx = 0UL; idx < oldCapacity; idx++) { if(oldEntry[idx].key != NULL) { _CDVJKDictionaryAddObject(dictionary, oldEntry[idx].keyHash, oldEntry[idx].key, oldEntry[idx].object); oldEntry[idx].keyHash = 0UL; oldEntry[idx].key = NULL; oldEntry[idx].object = NULL; } } + NSCParameterAssert((oldCount == dictionary->count)); + free(oldEntry); oldEntry = NULL; + } +} + +static CDVJKDictionary *_CDVJKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection) { + NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_CDVJKDictionaryClass != NULL) && (_CDVJKDictionaryInstanceSize > 0UL)); + CDVJKDictionary *dictionary = NULL; + if(CDVJK_EXPECT_T((dictionary = (CDVJKDictionary *)calloc(1UL, _CDVJKDictionaryInstanceSize)) != NULL)) { // Directly allocate the CDVJKDictionary instance via calloc. + dictionary->isa = _CDVJKDictionaryClass; + if((dictionary = [dictionary init]) == NULL) { return(NULL); } + dictionary->capacity = _CDVJKDictionaryCapacityForCount(count); + dictionary->count = 0UL; + + if(CDVJK_EXPECT_F((dictionary->entry = (CDVJKHashTableEntry *)calloc(1UL, sizeof(CDVJKHashTableEntry) * dictionary->capacity)) == NULL)) { [dictionary autorelease]; return(NULL); } + + NSUInteger idx = 0UL; + for(idx = 0UL; idx < count; idx++) { _CDVJKDictionaryAddObject(dictionary, keyHashes[idx], keys[idx], objects[idx]); } + + dictionary->mutations = (mutableCollection == NO) ? 0UL : 1UL; + } + return(dictionary); +} + +- (void)dealloc +{ + if(CDVJK_EXPECT_T(entry != NULL)) { + NSUInteger atEntry = 0UL; + for(atEntry = 0UL; atEntry < capacity; atEntry++) { + if(CDVJK_EXPECT_T(entry[atEntry].key != NULL)) { CFRelease(entry[atEntry].key); entry[atEntry].key = NULL; } + if(CDVJK_EXPECT_T(entry[atEntry].object != NULL)) { CFRelease(entry[atEntry].object); entry[atEntry].object = NULL; } + } + + free(entry); entry = NULL; + } + + [super dealloc]; +} + +static CDVJKHashTableEntry *_CDVJKDictionaryHashEntry(CDVJKDictionary *dictionary) { + NSCParameterAssert(dictionary != NULL); + return(dictionary->entry); +} + +static NSUInteger _CDVJKDictionaryCapacity(CDVJKDictionary *dictionary) { + NSCParameterAssert(dictionary != NULL); + return(dictionary->capacity); +} + +static void _CDVJKDictionaryRemoveObjectWithEntry(CDVJKDictionary *dictionary, CDVJKHashTableEntry *entry) { + NSCParameterAssert((dictionary != NULL) && (entry != NULL) && (entry->key != NULL) && (entry->object != NULL) && (dictionary->count > 0UL) && (dictionary->count <= dictionary->capacity)); + CFRelease(entry->key); entry->key = NULL; + CFRelease(entry->object); entry->object = NULL; + entry->keyHash = 0UL; + dictionary->count--; + // In order for certain invariants that are used to speed up the search for a particular key, we need to "re-add" all the entries in the hash table following this entry until we hit a NULL entry. + NSUInteger removeIdx = entry - dictionary->entry, idx = 0UL; + NSCParameterAssert((removeIdx < dictionary->capacity)); + for(idx = 0UL; idx < dictionary->capacity; idx++) { + NSUInteger entryIdx = (removeIdx + idx + 1UL) % dictionary->capacity; + CDVJKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; + if(atEntry->key == NULL) { break; } + NSUInteger keyHash = atEntry->keyHash; + id key = atEntry->key, object = atEntry->object; + NSCParameterAssert(object != NULL); + atEntry->keyHash = 0UL; + atEntry->key = NULL; + atEntry->object = NULL; + NSUInteger addKeyEntry = keyHash % dictionary->capacity, addIdx = 0UL; + for(addIdx = 0UL; addIdx < dictionary->capacity; addIdx++) { + CDVJKHashTableEntry *atAddEntry = &dictionary->entry[((addKeyEntry + addIdx) % dictionary->capacity)]; + if(CDVJK_EXPECT_T(atAddEntry->key == NULL)) { NSCParameterAssert((atAddEntry->keyHash == 0UL) && (atAddEntry->object == NULL)); atAddEntry->key = key; atAddEntry->object = object; atAddEntry->keyHash = keyHash; break; } + } + } +} + +static void _CDVJKDictionaryAddObject(CDVJKDictionary *dictionary, NSUInteger keyHash, id key, id object) { + NSCParameterAssert((dictionary != NULL) && (key != NULL) && (object != NULL) && (dictionary->count < dictionary->capacity) && (dictionary->entry != NULL)); + NSUInteger keyEntry = keyHash % dictionary->capacity, idx = 0UL; + for(idx = 0UL; idx < dictionary->capacity; idx++) { + NSUInteger entryIdx = (keyEntry + idx) % dictionary->capacity; + CDVJKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; + if(CDVJK_EXPECT_F(atEntry->keyHash == keyHash) && CDVJK_EXPECT_T(atEntry->key != NULL) && (CDVJK_EXPECT_F(key == atEntry->key) || CDVJK_EXPECT_F(CFEqual(atEntry->key, key)))) { _CDVJKDictionaryRemoveObjectWithEntry(dictionary, atEntry); } + if(CDVJK_EXPECT_T(atEntry->key == NULL)) { NSCParameterAssert((atEntry->keyHash == 0UL) && (atEntry->object == NULL)); atEntry->key = key; atEntry->object = object; atEntry->keyHash = keyHash; dictionary->count++; return; } + } + + // We should never get here. If we do, we -release the key / object because it's our responsibility. + CFRelease(key); + CFRelease(object); +} + +- (NSUInteger)count +{ + return(count); +} + +static CDVJKHashTableEntry *_CDVJKDictionaryHashTableEntryForKey(CDVJKDictionary *dictionary, id aKey) { + NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); + if((aKey == NULL) || (dictionary->capacity == 0UL)) { return(NULL); } + NSUInteger keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL; + CDVJKHashTableEntry *atEntry = NULL; + for(idx = 0UL; idx < dictionary->capacity; idx++) { + atEntry = &dictionary->entry[(keyEntry + idx) % dictionary->capacity]; + if(CDVJK_EXPECT_T(atEntry->keyHash == keyHash) && CDVJK_EXPECT_T(atEntry->key != NULL) && ((atEntry->key == aKey) || CFEqual(atEntry->key, aKey))) { NSCParameterAssert(atEntry->object != NULL); return(atEntry); break; } + if(CDVJK_EXPECT_F(atEntry->key == NULL)) { NSCParameterAssert(atEntry->object == NULL); return(NULL); break; } // If the key was in the table, we would have found it by now. + } + return(NULL); +} + +- (id)objectForKey:(id)aKey +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + CDVJKHashTableEntry *entryForKey = _CDVJKDictionaryHashTableEntryForKey(self, aKey); + return((entryForKey != NULL) ? entryForKey->object : NULL); +} + +- (void)getObjects:(id *)objects andKeys:(id *)keys +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL; + for(atEntry = 0UL; atEntry < capacity; atEntry++) { + if(CDVJK_EXPECT_T(entry[atEntry].key != NULL)) { + NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count)); + if(CDVJK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; } + if(CDVJK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; } + arrayIdx++; + } + } +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (entry != NULL) && (count <= capacity)); + if(CDVJK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } + if(CDVJK_EXPECT_F(state->state >= capacity)) { return(0UL); } + + NSUInteger enumeratedCount = 0UL; + while(CDVJK_EXPECT_T(enumeratedCount < len) && CDVJK_EXPECT_T(state->state < capacity)) { if(CDVJK_EXPECT_T(entry[state->state].key != NULL)) { stackbuf[enumeratedCount++] = entry[state->state].key; } state->state++; } + + return(enumeratedCount); +} + +- (NSEnumerator *)keyEnumerator +{ + return([[[CDVJKDictionaryEnumerator alloc] initWithJKDictionary:self] autorelease]); +} + +- (void)setObject:(id)anObject forKey:(id)aKey +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil value (key: %@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey]; } + + _CDVJKDictionaryResizeIfNeccessary(self); +#ifndef __clang_analyzer__ + aKey = [aKey copy]; // Why on earth would clang complain that this -copy "might leak", + anObject = [anObject retain]; // but this -retain doesn't!? +#endif // __clang_analyzer__ + _CDVJKDictionaryAddObject(self, CFHash(aKey), aKey, anObject); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)removeObjectForKey:(id)aKey +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to remove nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + CDVJKHashTableEntry *entryForKey = _CDVJKDictionaryHashTableEntryForKey(self, aKey); + if(entryForKey != NULL) { + _CDVJKDictionaryRemoveObjectWithEntry(self, entryForKey); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; + } +} + +- (id)copyWithZone:(NSZone *)zone +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + return((mutations == 0UL) ? [self retain] : [[NSDictionary allocWithZone:zone] initWithDictionary:self]); +} + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + return([[NSMutableDictionary allocWithZone:zone] initWithDictionary:self]); +} + +@end + + + +#pragma mark - + +CDVJK_STATIC_INLINE size_t cdvjk_min(size_t a, size_t b) { return((a < b) ? a : b); } +CDVJK_STATIC_INLINE size_t cdvjk_max(size_t a, size_t b) { return((a > b) ? a : b); } + +CDVJK_STATIC_INLINE CDVJKHash cdvcalculateHash(CDVJKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); } + +static void cdvjk_error(CDVJKParseState *parseState, NSString *format, ...) { + NSCParameterAssert((parseState != NULL) && (format != NULL)); + + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + +#if 0 + const unsigned char *lineStart = parseState->stringBuffer.bytes.ptr + parseState->lineStartIndex; + const unsigned char *lineEnd = lineStart; + const unsigned char *atCharacterPtr = NULL; + + for(atCharacterPtr = lineStart; atCharacterPtr < CDVJK_END_STRING_PTR(parseState); atCharacterPtr++) { lineEnd = atCharacterPtr; if(cdvjk_parse_is_newline(parseState, atCharacterPtr)) { break; } } + + NSString *lineString = @"", *carretString = @""; + if(lineStart < CDVJK_END_STRING_PTR(parseState)) { + lineString = [[[NSString alloc] initWithBytes:lineStart length:(lineEnd - lineStart) encoding:NSUTF8StringEncoding] autorelease]; + carretString = [NSString stringWithFormat:@"%*.*s^", (int)(parseState->atIndex - parseState->lineStartIndex), (int)(parseState->atIndex - parseState->lineStartIndex), " "]; + } +#endif + + if(parseState->error == NULL) { + parseState->error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + formatString, NSLocalizedDescriptionKey, + [NSNumber numberWithUnsignedLong:parseState->atIndex], @"CDVJKAtIndexKey", + [NSNumber numberWithUnsignedLong:parseState->lineNumber], @"CDVJKLineNumberKey", + //lineString, @"CDVJKErrorLine0Key", + //carretString, @"CDVJKErrorLine1Key", + NULL]]; + } +} + +#pragma mark - +#pragma mark Buffer and Object Stack management functions + +static void cdvjk_managedBuffer_release(CDVJKManagedBuffer *managedBuffer) { + if((managedBuffer->flags & CDVJKManagedBufferMustFree)) { + if(managedBuffer->bytes.ptr != NULL) { free(managedBuffer->bytes.ptr); managedBuffer->bytes.ptr = NULL; } + managedBuffer->flags &= ~CDVJKManagedBufferMustFree; + } + + managedBuffer->bytes.ptr = NULL; + managedBuffer->bytes.length = 0UL; + managedBuffer->flags &= ~CDVJKManagedBufferLocationMask; +} + +static void cdvjk_managedBuffer_setToStackBuffer(CDVJKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length) { + cdvjk_managedBuffer_release(managedBuffer); + managedBuffer->bytes.ptr = ptr; + managedBuffer->bytes.length = length; + managedBuffer->flags = (managedBuffer->flags & ~CDVJKManagedBufferLocationMask) | CDVJKManagedBufferOnStack; +} + +static unsigned char *cdvjk_managedBuffer_resize(CDVJKManagedBuffer *managedBuffer, size_t newSize) { + size_t roundedUpNewSize = newSize; + + if(managedBuffer->roundSizeUpToMultipleOf > 0UL) { roundedUpNewSize = newSize + ((managedBuffer->roundSizeUpToMultipleOf - (newSize % managedBuffer->roundSizeUpToMultipleOf)) % managedBuffer->roundSizeUpToMultipleOf); } + + if((roundedUpNewSize != managedBuffer->bytes.length) && (roundedUpNewSize > managedBuffer->bytes.length)) { + if((managedBuffer->flags & CDVJKManagedBufferLocationMask) == CDVJKManagedBufferOnStack) { + NSCParameterAssert((managedBuffer->flags & CDVJKManagedBufferMustFree) == 0); + unsigned char *newBuffer = NULL, *oldBuffer = managedBuffer->bytes.ptr; + + if((newBuffer = (unsigned char *)malloc(roundedUpNewSize)) == NULL) { return(NULL); } + memcpy(newBuffer, oldBuffer, cdvjk_min(managedBuffer->bytes.length, roundedUpNewSize)); + managedBuffer->flags = (managedBuffer->flags & ~CDVJKManagedBufferLocationMask) | (CDVJKManagedBufferOnHeap | CDVJKManagedBufferMustFree); + managedBuffer->bytes.ptr = newBuffer; + managedBuffer->bytes.length = roundedUpNewSize; + } else { + NSCParameterAssert(((managedBuffer->flags & CDVJKManagedBufferMustFree) != 0) && ((managedBuffer->flags & CDVJKManagedBufferLocationMask) == CDVJKManagedBufferOnHeap)); + if((managedBuffer->bytes.ptr = (unsigned char *)reallocf(managedBuffer->bytes.ptr, roundedUpNewSize)) == NULL) { return(NULL); } + managedBuffer->bytes.length = roundedUpNewSize; + } + } + + return(managedBuffer->bytes.ptr); +} + + + +static void cdvjk_objectStack_release(CDVJKObjectStack *objectStack) { + NSCParameterAssert(objectStack != NULL); + + NSCParameterAssert(objectStack->index <= objectStack->count); + size_t atIndex = 0UL; + for(atIndex = 0UL; atIndex < objectStack->index; atIndex++) { + if(objectStack->objects[atIndex] != NULL) { CFRelease(objectStack->objects[atIndex]); objectStack->objects[atIndex] = NULL; } + if(objectStack->keys[atIndex] != NULL) { CFRelease(objectStack->keys[atIndex]); objectStack->keys[atIndex] = NULL; } + } + objectStack->index = 0UL; + + if(objectStack->flags & CDVJKObjectStackMustFree) { + NSCParameterAssert((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnHeap); + if(objectStack->objects != NULL) { free(objectStack->objects); objectStack->objects = NULL; } + if(objectStack->keys != NULL) { free(objectStack->keys); objectStack->keys = NULL; } + if(objectStack->cfHashes != NULL) { free(objectStack->cfHashes); objectStack->cfHashes = NULL; } + objectStack->flags &= ~CDVJKObjectStackMustFree; + } + + objectStack->objects = NULL; + objectStack->keys = NULL; + objectStack->cfHashes = NULL; + + objectStack->count = 0UL; + objectStack->flags &= ~CDVJKObjectStackLocationMask; +} + +static void cdvjk_objectStack_setToStackBuffer(CDVJKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count) { + NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (cfHashes != NULL) && (count > 0UL)); + cdvjk_objectStack_release(objectStack); + objectStack->objects = objects; + objectStack->keys = keys; + objectStack->cfHashes = cfHashes; + objectStack->count = count; + objectStack->flags = (objectStack->flags & ~CDVJKObjectStackLocationMask) | CDVJKObjectStackOnStack; +#ifndef NS_BLOCK_ASSERTIONS + size_t idx; + for(idx = 0UL; idx < objectStack->count; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } +#endif +} + +static int cdvjk_objectStack_resize(CDVJKObjectStack *objectStack, size_t newCount) { + size_t roundedUpNewCount = newCount; + int returnCode = 0; + + void **newObjects = NULL, **newKeys = NULL; + CFHashCode *newCFHashes = NULL; + + if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); } + + if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) { + if((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnStack) { + NSCParameterAssert((objectStack->flags & CDVJKObjectStackMustFree) == 0); + + if((newObjects = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newObjects, objectStack->objects, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); + if((newKeys = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newKeys, objectStack->keys, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); + + if((newCFHashes = (CFHashCode *)calloc(1UL, roundedUpNewCount * sizeof(CFHashCode))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newCFHashes, objectStack->cfHashes, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(CFHashCode)); + + objectStack->flags = (objectStack->flags & ~CDVJKObjectStackLocationMask) | (CDVJKObjectStackOnHeap | CDVJKObjectStackMustFree); + objectStack->objects = newObjects; newObjects = NULL; + objectStack->keys = newKeys; newKeys = NULL; + objectStack->cfHashes = newCFHashes; newCFHashes = NULL; + objectStack->count = roundedUpNewCount; + } else { + NSCParameterAssert(((objectStack->flags & CDVJKObjectStackMustFree) != 0) && ((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnHeap)); + if((newObjects = (void ** )realloc(objectStack->objects, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->objects = newObjects; newObjects = NULL; } else { returnCode = 1; goto errorExit; } + if((newKeys = (void ** )realloc(objectStack->keys, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->keys = newKeys; newKeys = NULL; } else { returnCode = 1; goto errorExit; } + if((newCFHashes = (CFHashCode *)realloc(objectStack->cfHashes, roundedUpNewCount * sizeof(CFHashCode))) != NULL) { objectStack->cfHashes = newCFHashes; newCFHashes = NULL; } else { returnCode = 1; goto errorExit; } + +#ifndef NS_BLOCK_ASSERTIONS + size_t idx; + for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } +#endif + objectStack->count = roundedUpNewCount; + } + } + + errorExit: + if(newObjects != NULL) { free(newObjects); newObjects = NULL; } + if(newKeys != NULL) { free(newKeys); newKeys = NULL; } + if(newCFHashes != NULL) { free(newCFHashes); newCFHashes = NULL; } + + return(returnCode); +} + +//////////// +#pragma mark - +#pragma mark Unicode related functions + +CDVJK_STATIC_INLINE CDV_ConversionResult cdvisValidCodePoint(UTF32 *u32CodePoint) { + CDV_ConversionResult result = conversionOK; + UTF32 ch = *u32CodePoint; + + if(CDVJK_EXPECT_F(ch >= CDV_UNI_SUR_HIGH_START) && (CDVJK_EXPECT_T(ch <= CDV_UNI_SUR_LOW_END))) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } + if(CDVJK_EXPECT_F(ch >= 0xFDD0U) && (CDVJK_EXPECT_F(ch <= 0xFDEFU) || CDVJK_EXPECT_F((ch & 0xFFFEU) == 0xFFFEU)) && CDVJK_EXPECT_T(ch <= 0x10FFFFU)) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } + if(CDVJK_EXPECT_F(ch == 0U)) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } + + finished: + *u32CodePoint = ch; + return(result); +} + + +static int cdvisLegalUTF8(const UTF8 *source, size_t length) { + const UTF8 *srcptr = source + length; + UTF8 a; + + switch(length) { + default: return(0); // Everything else falls through when "true"... + case 4: if(CDVJK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } + case 3: if(CDVJK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } + case 2: if(CDVJK_EXPECT_F( (a = (*--srcptr)) > 0xBF )) { return(0); } + + switch(*source) { // no fall-through in this inner switch + case 0xE0: if(CDVJK_EXPECT_F(a < 0xA0)) { return(0); } break; + case 0xED: if(CDVJK_EXPECT_F(a > 0x9F)) { return(0); } break; + case 0xF0: if(CDVJK_EXPECT_F(a < 0x90)) { return(0); } break; + case 0xF4: if(CDVJK_EXPECT_F(a > 0x8F)) { return(0); } break; + default: if(CDVJK_EXPECT_F(a < 0x80)) { return(0); } + } + + case 1: if(CDVJK_EXPECT_F((CDVJK_EXPECT_T(*source < 0xC2)) && CDVJK_EXPECT_F(*source >= 0x80))) { return(0); } + } + + if(CDVJK_EXPECT_F(*source > 0xF4)) { return(0); } + + return(1); +} + +static CDV_ConversionResult cdvConvertSingleCodePointInUTF8(const UTF8 *sourceStart, const UTF8 *sourceEnd, UTF8 const **nextUTF8, UTF32 *convertedUTF32) { + CDV_ConversionResult result = conversionOK; + const UTF8 *source = sourceStart; + UTF32 ch = 0UL; + +#if !defined(CDVJK_FAST_TRAILING_BYTES) + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; +#else + unsigned short extraBytesToRead = __builtin_clz(((*source)^0xff) << 25); +#endif + + if(CDVJK_EXPECT_F((source + extraBytesToRead + 1) > sourceEnd) || CDVJK_EXPECT_F(!cdvisLegalUTF8(source, extraBytesToRead + 1))) { + source++; + while((source < sourceEnd) && (((*source) & 0xc0) == 0x80) && ((source - sourceStart) < (extraBytesToRead + 1))) { source++; } + NSCParameterAssert(source <= sourceEnd); + result = ((source < sourceEnd) && (((*source) & 0xc0) != 0x80)) ? sourceIllegal : ((sourceStart + extraBytesToRead + 1) > sourceEnd) ? sourceExhausted : sourceIllegal; + ch = CDV_UNI_REPLACEMENT_CHAR; + goto finished; + } + + switch(extraBytesToRead) { // The cases all fall through. + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + result = cdvisValidCodePoint(&ch); + + finished: + *nextUTF8 = source; + *convertedUTF32 = ch; + + return(result); +} + + +static CDV_ConversionResult cdvConvertUTF32toUTF8 (UTF32 u32CodePoint, UTF8 **targetStart, UTF8 *targetEnd) { + const UTF32 byteMask = 0xBF, byteMark = 0x80; + CDV_ConversionResult result = conversionOK; + UTF8 *target = *targetStart; + UTF32 ch = u32CodePoint; + unsigned short bytesToWrite = 0; + + result = cdvisValidCodePoint(&ch); + + // Figure out how many bytes the result will require. Turn any illegally large UTF32 things (> Plane 17) into replacement chars. + if(ch < (UTF32)0x80) { bytesToWrite = 1; } + else if(ch < (UTF32)0x800) { bytesToWrite = 2; } + else if(ch < (UTF32)0x10000) { bytesToWrite = 3; } + else if(ch <= CDV_UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; } + else { bytesToWrite = 3; ch = CDV_UNI_REPLACEMENT_CHAR; result = sourceIllegal; } + + target += bytesToWrite; + if (target > targetEnd) { target -= bytesToWrite; result = targetExhausted; goto finished; } + + switch (bytesToWrite) { // note: everything falls through. + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + + target += bytesToWrite; + + finished: + *targetStart = target; + return(result); +} + +CDVJK_STATIC_INLINE int cdvjk_string_add_unicodeCodePoint(CDVJKParseState *parseState, uint32_t unicodeCodePoint, size_t *tokenBufferIdx, CDVJKHash *stringHash) { + UTF8 *u8s = &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx]; + CDV_ConversionResult result; + + if((result = cdvConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } } + size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len; + + while(*tokenBufferIdx < nextIdx) { *stringHash = cdvcalculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); } + + return(0); +} + +//////////// +#pragma mark - +#pragma mark Decoding / parsing / deserializing functions + +static int cdvjk_parse_string(CDVJKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); + const unsigned char *stringStart = CDVJK_AT_STRING_PTR(parseState) + 1; + const unsigned char *endOfBuffer = CDVJK_END_STRING_PTR(parseState); + const unsigned char *atStringCharacter = stringStart; + unsigned char *tokenBuffer = parseState->token.tokenBuffer.bytes.ptr; + size_t tokenStartIndex = parseState->atIndex; + size_t tokenBufferIdx = 0UL; + + int onlySimpleString = 1, stringState = CDVJSONStringStateStart; + uint16_t escapedUnicode1 = 0U, escapedUnicode2 = 0U; + uint32_t escapedUnicodeCodePoint = 0U; + CDVJKHash stringHash = CDVJK_HASH_INIT; + + while(1) { + unsigned long currentChar; + + if(CDVJK_EXPECT_F(atStringCharacter == endOfBuffer)) { /* XXX Add error message */ stringState = CDVJSONStringStateError; goto finishedParsing; } + + if(CDVJK_EXPECT_F((currentChar = *atStringCharacter++) >= 0x80UL)) { + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + CDV_ConversionResult result; + + if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; } + stringHash = cdvcalculateHash(stringHash, currentChar); + while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState)); stringHash = cdvcalculateHash(stringHash, *atStringCharacter++); } + continue; + } else { + if(CDVJK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = CDVJSONStringStateFinished; goto finishedParsing; } + + if(CDVJK_EXPECT_F(currentChar == (unsigned long)'\\')) { + switchToSlowPath: + onlySimpleString = 0; + stringState = CDVJSONStringStateParsing; + tokenBufferIdx = (atStringCharacter - stringStart) - 1L; + if(CDVJK_EXPECT_F((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length)) { if((tokenBuffer = cdvjk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { cdvjk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } + memcpy(tokenBuffer, stringStart, tokenBufferIdx); + goto slowMatch; + } + + if(CDVJK_EXPECT_F(currentChar < 0x20UL)) { cdvjk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; } + + stringHash = cdvcalculateHash(stringHash, currentChar); + } + } + + slowMatch: + + for(atStringCharacter = (stringStart + ((atStringCharacter - stringStart) - 1L)); (atStringCharacter < endOfBuffer) && (tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); atStringCharacter++) { + if((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length) { if((tokenBuffer = cdvjk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { cdvjk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } + + NSCParameterAssert(tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); + + unsigned long currentChar = (*atStringCharacter), escapedChar; + + if(CDVJK_EXPECT_T(stringState == CDVJSONStringStateParsing)) { + if(CDVJK_EXPECT_T(currentChar >= 0x20UL)) { + if(CDVJK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence + if(CDVJK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = CDVJSONStringStateFinished; atStringCharacter++; goto finishedParsing; } + if(CDVJK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = CDVJSONStringStateEscape; continue; } + stringHash = cdvcalculateHash(stringHash, currentChar); + tokenBuffer[tokenBufferIdx++] = currentChar; + continue; + } else { // UTF8 sequence + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + CDV_ConversionResult result; + + if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(atStringCharacter, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { + if((result == sourceIllegal) && ((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0)) { cdvjk_error(parseState, @"Illegal UTF8 sequence found in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; } + if(result == sourceExhausted) { cdvjk_error(parseState, @"End of buffer reached while parsing UTF8 in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; } + if(cdvjk_string_add_unicodeCodePoint(parseState, u32ch, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } + atStringCharacter = nextValidCharacter - 1; + continue; + } else { + while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = cdvcalculateHash(stringHash, *atStringCharacter++); } + atStringCharacter--; + continue; + } + } + } else { // currentChar < 0x20 + cdvjk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; + } + + } else { // stringState != CDVJSONStringStateParsing + int isSurrogate = 1; + + switch(stringState) { + case CDVJSONStringStateEscape: + switch(currentChar) { + case 'u': escapedUnicode1 = 0U; escapedUnicode2 = 0U; escapedUnicodeCodePoint = 0U; stringState = CDVJSONStringStateEscapedUnicode1; break; + + case 'b': escapedChar = '\b'; goto parsedEscapedChar; + case 'f': escapedChar = '\f'; goto parsedEscapedChar; + case 'n': escapedChar = '\n'; goto parsedEscapedChar; + case 'r': escapedChar = '\r'; goto parsedEscapedChar; + case 't': escapedChar = '\t'; goto parsedEscapedChar; + case '\\': escapedChar = '\\'; goto parsedEscapedChar; + case '/': escapedChar = '/'; goto parsedEscapedChar; + case '"': escapedChar = '"'; goto parsedEscapedChar; + + parsedEscapedChar: + stringState = CDVJSONStringStateParsing; + stringHash = cdvcalculateHash(stringHash, escapedChar); + tokenBuffer[tokenBufferIdx++] = escapedChar; + break; + + default: cdvjk_error(parseState, @"Invalid escape sequence found in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; break; + } + break; + + case CDVJSONStringStateEscapedUnicode1: + case CDVJSONStringStateEscapedUnicode2: + case CDVJSONStringStateEscapedUnicode3: + case CDVJSONStringStateEscapedUnicode4: isSurrogate = 0; + case CDVJSONStringStateEscapedUnicodeSurrogate1: + case CDVJSONStringStateEscapedUnicodeSurrogate2: + case CDVJSONStringStateEscapedUnicodeSurrogate3: + case CDVJSONStringStateEscapedUnicodeSurrogate4: + { + uint16_t hexValue = 0U; + + switch(currentChar) { + case '0' ... '9': hexValue = currentChar - '0'; goto parsedHex; + case 'a' ... 'f': hexValue = (currentChar - 'a') + 10U; goto parsedHex; + case 'A' ... 'F': hexValue = (currentChar - 'A') + 10U; goto parsedHex; + + parsedHex: + if(!isSurrogate) { escapedUnicode1 = (escapedUnicode1 << 4) | hexValue; } else { escapedUnicode2 = (escapedUnicode2 << 4) | hexValue; } + + if(stringState == CDVJSONStringStateEscapedUnicode4) { + if(((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xE000U))) { + if((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xDC00U)) { stringState = CDVJSONStringStateEscapedNeedEscapeForSurrogate; } + else if((escapedUnicode1 >= 0xDC00U) && (escapedUnicode1 < 0xE000U)) { + if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = CDV_UNI_REPLACEMENT_CHAR; } + else { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } + } + } + else { escapedUnicodeCodePoint = escapedUnicode1; } + } + + if(stringState == CDVJSONStringStateEscapedUnicodeSurrogate4) { + if((escapedUnicode2 < 0xdc00) || (escapedUnicode2 > 0xdfff)) { + if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = CDV_UNI_REPLACEMENT_CHAR; } + else { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } + } + else { escapedUnicodeCodePoint = ((escapedUnicode1 - 0xd800) * 0x400) + (escapedUnicode2 - 0xdc00) + 0x10000; } + } + + if((stringState == CDVJSONStringStateEscapedUnicode4) || (stringState == CDVJSONStringStateEscapedUnicodeSurrogate4)) { + if((cdvisValidCodePoint(&escapedUnicodeCodePoint) == sourceIllegal) && ((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0)) { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } + stringState = CDVJSONStringStateParsing; + if(cdvjk_string_add_unicodeCodePoint(parseState, escapedUnicodeCodePoint, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } + } + else if((stringState >= CDVJSONStringStateEscapedUnicode1) && (stringState <= CDVJSONStringStateEscapedUnicodeSurrogate4)) { stringState++; } + break; + + default: cdvjk_error(parseState, @"Unexpected character found in \\u Unicode escape sequence. Found '%c', expected [0-9a-fA-F].", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; break; + } + } + break; + + case CDVJSONStringStateEscapedNeedEscapeForSurrogate: + if(currentChar == '\\') { stringState = CDVJSONStringStateEscapedNeedEscapedUForSurrogate; } + else { + if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0) { cdvjk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } + else { stringState = CDVJSONStringStateParsing; atStringCharacter--; if(cdvjk_string_add_unicodeCodePoint(parseState, CDV_UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } + } + break; + + case CDVJSONStringStateEscapedNeedEscapedUForSurrogate: + if(currentChar == 'u') { stringState = CDVJSONStringStateEscapedUnicodeSurrogate1; } + else { + if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0) { cdvjk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } + else { stringState = CDVJSONStringStateParsing; atStringCharacter -= 2; if(cdvjk_string_add_unicodeCodePoint(parseState, CDV_UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } + } + break; + + default: cdvjk_error(parseState, @"Internal error: Unknown stringState. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; break; + } + } + } + +finishedParsing: + + if(CDVJK_EXPECT_T(stringState == CDVJSONStringStateFinished)) { + NSCParameterAssert((parseState->stringBuffer.bytes.ptr + tokenStartIndex) < atStringCharacter); + + parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + tokenStartIndex; + parseState->token.tokenPtrRange.length = (atStringCharacter - parseState->token.tokenPtrRange.ptr); + + if(CDVJK_EXPECT_T(onlySimpleString)) { + NSCParameterAssert(((parseState->token.tokenPtrRange.ptr + 1) < endOfBuffer) && (parseState->token.tokenPtrRange.length >= 2UL) && (((parseState->token.tokenPtrRange.ptr + 1) + (parseState->token.tokenPtrRange.length - 2)) < endOfBuffer)); + parseState->token.value.ptrRange.ptr = parseState->token.tokenPtrRange.ptr + 1; + parseState->token.value.ptrRange.length = parseState->token.tokenPtrRange.length - 2UL; + } else { + parseState->token.value.ptrRange.ptr = parseState->token.tokenBuffer.bytes.ptr; + parseState->token.value.ptrRange.length = tokenBufferIdx; + } + + parseState->token.value.hash = stringHash; + parseState->token.value.type = CDVJKValueTypeString; + parseState->atIndex = (atStringCharacter - parseState->stringBuffer.bytes.ptr); + } + + if(CDVJK_EXPECT_F(stringState != CDVJSONStringStateFinished)) { cdvjk_error(parseState, @"Invalid string."); } + return(CDVJK_EXPECT_T(stringState == CDVJSONStringStateFinished) ? 0 : 1); +} + +static int cdvjk_parse_number(CDVJKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); + const unsigned char *numberStart = CDVJK_AT_STRING_PTR(parseState); + const unsigned char *endOfBuffer = CDVJK_END_STRING_PTR(parseState); + const unsigned char *atNumberCharacter = NULL; + int numberState = CDVJSONNumberStateWholeNumberStart, isFloatingPoint = 0, isNegative = 0, backup = 0; + size_t startingIndex = parseState->atIndex; + + for(atNumberCharacter = numberStart; (CDVJK_EXPECT_T(atNumberCharacter < endOfBuffer)) && (CDVJK_EXPECT_T(!(CDVJK_EXPECT_F(numberState == CDVJSONNumberStateFinished) || CDVJK_EXPECT_F(numberState == CDVJSONNumberStateError)))); atNumberCharacter++) { + unsigned long currentChar = (unsigned long)(*atNumberCharacter), lowerCaseCC = currentChar | 0x20UL; + + switch(numberState) { + case CDVJSONNumberStateWholeNumberStart: if (currentChar == '-') { numberState = CDVJSONNumberStateWholeNumberMinus; isNegative = 1; break; } + case CDVJSONNumberStateWholeNumberMinus: if (currentChar == '0') { numberState = CDVJSONNumberStateWholeNumberZero; break; } + else if( (currentChar >= '1') && (currentChar <= '9')) { numberState = CDVJSONNumberStateWholeNumber; break; } + else { /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; } + case CDVJSONNumberStateExponentStart: if( (currentChar == '+') || (currentChar == '-')) { numberState = CDVJSONNumberStateExponentPlusMinus; break; } + case CDVJSONNumberStateFractionalNumberStart: + case CDVJSONNumberStateExponentPlusMinus:if(!((currentChar >= '0') && (currentChar <= '9'))) { /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; } + else { if(numberState == CDVJSONNumberStateFractionalNumberStart) { numberState = CDVJSONNumberStateFractionalNumber; } + else { numberState = CDVJSONNumberStateExponent; } break; } + case CDVJSONNumberStateWholeNumberZero: + case CDVJSONNumberStateWholeNumber: if (currentChar == '.') { numberState = CDVJSONNumberStateFractionalNumberStart; isFloatingPoint = 1; break; } + case CDVJSONNumberStateFractionalNumber: if (lowerCaseCC == 'e') { numberState = CDVJSONNumberStateExponentStart; isFloatingPoint = 1; break; } + case CDVJSONNumberStateExponent: if(!((currentChar >= '0') && (currentChar <= '9')) || (numberState == CDVJSONNumberStateWholeNumberZero)) { numberState = CDVJSONNumberStateFinished; backup = 1; break; } + break; + default: /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; + } + } + + parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + startingIndex; + parseState->token.tokenPtrRange.length = (atNumberCharacter - parseState->token.tokenPtrRange.ptr) - backup; + parseState->atIndex = (parseState->token.tokenPtrRange.ptr + parseState->token.tokenPtrRange.length) - parseState->stringBuffer.bytes.ptr; + + if(CDVJK_EXPECT_T(numberState == CDVJSONNumberStateFinished)) { + unsigned char numberTempBuf[parseState->token.tokenPtrRange.length + 4UL]; + unsigned char *endOfNumber = NULL; + + memcpy(numberTempBuf, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length); + numberTempBuf[parseState->token.tokenPtrRange.length] = 0; + + errno = 0; + + // Treat "-0" as a floating point number, which is capable of representing negative zeros. + if(CDVJK_EXPECT_F(parseState->token.tokenPtrRange.length == 2UL) && CDVJK_EXPECT_F(numberTempBuf[1] == '0') && CDVJK_EXPECT_F(isNegative)) { isFloatingPoint = 1; } + + if(isFloatingPoint) { + parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber); // strtod is documented to return U+2261 (identical to) 0.0 on an underflow error (along with setting errno to ERANGE). + parseState->token.value.type = CDVJKValueTypeDouble; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.doubleValue; + parseState->token.value.ptrRange.length = sizeof(double); + parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type); + } else { + if(isNegative) { + parseState->token.value.number.longLongValue = strtoll((const char *)numberTempBuf, (char **)&endOfNumber, 10); + parseState->token.value.type = CDVJKValueTypeLongLong; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.longLongValue; + parseState->token.value.ptrRange.length = sizeof(long long); + parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type) + (CDVJKHash)parseState->token.value.number.longLongValue; + } else { + parseState->token.value.number.unsignedLongLongValue = strtoull((const char *)numberTempBuf, (char **)&endOfNumber, 10); + parseState->token.value.type = CDVJKValueTypeUnsignedLongLong; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.unsignedLongLongValue; + parseState->token.value.ptrRange.length = sizeof(unsigned long long); + parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type) + (CDVJKHash)parseState->token.value.number.unsignedLongLongValue; + } + } + + if(CDVJK_EXPECT_F(errno != 0)) { + numberState = CDVJSONNumberStateError; + if(errno == ERANGE) { + switch(parseState->token.value.type) { + case CDVJKValueTypeDouble: cdvjk_error(parseState, @"The value '%s' could not be represented as a 'double' due to %s.", numberTempBuf, (parseState->token.value.number.doubleValue == 0.0) ? "underflow" : "overflow"); break; // see above for == 0.0. + case CDVJKValueTypeLongLong: cdvjk_error(parseState, @"The value '%s' exceeded the minimum value that could be represented: %lld.", numberTempBuf, parseState->token.value.number.longLongValue); break; + case CDVJKValueTypeUnsignedLongLong: cdvjk_error(parseState, @"The value '%s' exceeded the maximum value that could be represented: %llu.", numberTempBuf, parseState->token.value.number.unsignedLongLongValue); break; + default: cdvjk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + } + } + if(CDVJK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && CDVJK_EXPECT_F(numberState != CDVJSONNumberStateError)) { numberState = CDVJSONNumberStateError; cdvjk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); } + + size_t hashIndex = 0UL; + for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = cdvcalculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); } + } + + if(CDVJK_EXPECT_F(numberState != CDVJSONNumberStateFinished)) { cdvjk_error(parseState, @"Invalid number."); } + return(CDVJK_EXPECT_T((numberState == CDVJSONNumberStateFinished)) ? 0 : 1); +} + +CDVJK_STATIC_INLINE void cdvjk_set_parsed_token(CDVJKParseState *parseState, const unsigned char *ptr, size_t length, CDVJKTokenType type, size_t advanceBy) { + parseState->token.tokenPtrRange.ptr = ptr; + parseState->token.tokenPtrRange.length = length; + parseState->token.type = type; + parseState->atIndex += advanceBy; +} + +static size_t cdvjk_parse_is_newline(CDVJKParseState *parseState, const unsigned char *atCharacterPtr) { + NSCParameterAssert((parseState != NULL) && (atCharacterPtr != NULL) && (atCharacterPtr >= parseState->stringBuffer.bytes.ptr) && (atCharacterPtr < CDVJK_END_STRING_PTR(parseState))); + const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); + + if(CDVJK_EXPECT_F(atCharacterPtr >= endOfStringPtr)) { return(0UL); } + + if(CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '\n')) { return(1UL); } + if(CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '\r')) { if((CDVJK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr)) && ((*(atCharacterPtr + 1)) == '\n')) { return(2UL); } return(1UL); } + if(parseState->parseOptionFlags & CDVJKParseOptionUnicodeNewlines) { + if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == 0xc2)) && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x85))) { return(2UL); } + if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == 0xe2)) && (((atCharacterPtr + 2) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x80) && (((*(atCharacterPtr + 2)) == 0xa8) || ((*(atCharacterPtr + 2)) == 0xa9)))) { return(3UL); } + } + + return(0UL); +} + +CDVJK_STATIC_INLINE int cdvjk_parse_skip_newline(CDVJKParseState *parseState) { + size_t newlineAdvanceAtIndex = 0UL; + if(CDVJK_EXPECT_F((newlineAdvanceAtIndex = cdvjk_parse_is_newline(parseState, CDVJK_AT_STRING_PTR(parseState))) > 0UL)) { parseState->lineNumber++; parseState->atIndex += (newlineAdvanceAtIndex - 1UL); parseState->lineStartIndex = parseState->atIndex + 1UL; return(1); } + return(0); +} + +CDVJK_STATIC_INLINE void cdvjk_parse_skip_whitespace(CDVJKParseState *parseState) { +#ifndef __clang_analyzer__ + NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); + const unsigned char *atCharacterPtr = NULL; + const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); + + for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { + if(((*(atCharacterPtr + 0)) == ' ') || ((*(atCharacterPtr + 0)) == '\t')) { continue; } + if(cdvjk_parse_skip_newline(parseState)) { continue; } + if(parseState->parseOptionFlags & CDVJKParseOptionComments) { + if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '/')) && (CDVJK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr))) { + if((*(atCharacterPtr + 1)) == '/') { + parseState->atIndex++; + for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { if(cdvjk_parse_skip_newline(parseState)) { break; } } + continue; + } + if((*(atCharacterPtr + 1)) == '*') { + parseState->atIndex++; + for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { + if(cdvjk_parse_skip_newline(parseState)) { continue; } + if(((*(atCharacterPtr + 0)) == '*') && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == '/'))) { parseState->atIndex++; break; } + } + continue; + } + } + } + break; + } +#endif +} + +static int cdvjk_parse_next_token(CDVJKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); + const unsigned char *atCharacterPtr = NULL; + const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); + unsigned char currentCharacter = 0U; + int stopParsing = 0; + + parseState->prev_atIndex = parseState->atIndex; + parseState->prev_lineNumber = parseState->lineNumber; + parseState->prev_lineStartIndex = parseState->lineStartIndex; + + cdvjk_parse_skip_whitespace(parseState); + + if((CDVJK_AT_STRING_PTR(parseState) == endOfStringPtr)) { stopParsing = 1; } + + if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr))) { + currentCharacter = *atCharacterPtr; + + if(CDVJK_EXPECT_T(currentCharacter == '"')) { if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_string(parseState)) == 0)) { cdvjk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, CDVJKTokenTypeString, 0UL); } } + else if(CDVJK_EXPECT_T(currentCharacter == ':')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeSeparator, 1UL); } + else if(CDVJK_EXPECT_T(currentCharacter == ',')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeComma, 1UL); } + else if((CDVJK_EXPECT_T(currentCharacter >= '0') && CDVJK_EXPECT_T(currentCharacter <= '9')) || CDVJK_EXPECT_T(currentCharacter == '-')) { if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_number(parseState)) == 0)) { cdvjk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, CDVJKTokenTypeNumber, 0UL); } } + else if(CDVJK_EXPECT_T(currentCharacter == '{')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeObjectBegin, 1UL); } + else if(CDVJK_EXPECT_T(currentCharacter == '}')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeObjectEnd, 1UL); } + else if(CDVJK_EXPECT_T(currentCharacter == '[')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeArrayBegin, 1UL); } + else if(CDVJK_EXPECT_T(currentCharacter == ']')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeArrayEnd, 1UL); } + + else if(CDVJK_EXPECT_T(currentCharacter == 't')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'r')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'u')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 4UL, CDVJKTokenTypeTrue, 4UL); } } + else if(CDVJK_EXPECT_T(currentCharacter == 'f')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 5UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'a')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'l')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 's')) && (CDVJK_EXPECT_T(atCharacterPtr[4] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 5UL, CDVJKTokenTypeFalse, 5UL); } } + else if(CDVJK_EXPECT_T(currentCharacter == 'n')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'u')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'l')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 'l')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 4UL, CDVJKTokenTypeNull, 4UL); } } + else { stopParsing = 1; /* XXX Add error message */ } + } + + if(CDVJK_EXPECT_F(stopParsing)) { cdvjk_error(parseState, @"Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '\"STRING\"', 'NUMBER'."); } + return(stopParsing); +} + +static void cdvjk_error_parse_accept_or3(CDVJKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String) { + NSString *acceptStrings[16]; + int acceptIdx = 0; + if(state & CDVJKParseAcceptValue) { acceptStrings[acceptIdx++] = or1String; } + if(state & CDVJKParseAcceptComma) { acceptStrings[acceptIdx++] = or2String; } + if(state & CDVJKParseAcceptEnd) { acceptStrings[acceptIdx++] = or3String; } + if(acceptIdx == 1) { cdvjk_error(parseState, @"Expected %@, not '%*.*s'", acceptStrings[0], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } + else if(acceptIdx == 2) { cdvjk_error(parseState, @"Expected %@ or %@, not '%*.*s'", acceptStrings[0], acceptStrings[1], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } + else if(acceptIdx == 3) { cdvjk_error(parseState, @"Expected %@, %@, or %@, not '%*.*s", acceptStrings[0], acceptStrings[1], acceptStrings[2], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } +} + +static void *cdvjk_parse_array(CDVJKParseState *parseState) { + size_t startingObjectIndex = parseState->objectStack.index; + int arrayState = CDVJKParseAcceptValueOrEnd, stopParsing = 0; + void *parsedArray = NULL; + + while(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { + if(CDVJK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(cdvjk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { cdvjk_error(parseState, @"Internal error: [array] objectsIndex > %zu, resize failed? %@ line %#ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } + + if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)) { + void *object = NULL; +#ifndef NS_BLOCK_ASSERTIONS + parseState->objectStack.objects[parseState->objectStack.index] = NULL; + parseState->objectStack.keys [parseState->objectStack.index] = NULL; +#endif + switch(parseState->token.type) { + case CDVJKTokenTypeNumber: + case CDVJKTokenTypeString: + case CDVJKTokenTypeTrue: + case CDVJKTokenTypeFalse: + case CDVJKTokenTypeNull: + case CDVJKTokenTypeArrayBegin: + case CDVJKTokenTypeObjectBegin: + if(CDVJK_EXPECT_F((arrayState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } + if(CDVJK_EXPECT_F((object = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Object == NULL"); stopParsing = 1; break; } else { parseState->objectStack.objects[parseState->objectStack.index++] = object; arrayState = CDVJKParseAcceptCommaOrEnd; } + break; + case CDVJKTokenTypeArrayEnd: if(CDVJK_EXPECT_T(arrayState & CDVJKParseAcceptEnd)) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedArray = (void *)_CDVJKArrayCreate((id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ']'."); } stopParsing = 1; break; + case CDVJKTokenTypeComma: if(CDVJK_EXPECT_T(arrayState & CDVJKParseAcceptComma)) { arrayState = CDVJKParseAcceptValue; } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; + default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, arrayState, @"a value", @"a comma", @"a ']'"); stopParsing = 1; break; + } + } + } + + if(CDVJK_EXPECT_F(parsedArray == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } +#if !defined(NS_BLOCK_ASSERTIONS) + else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } +#endif + + parseState->objectStack.index = startingObjectIndex; + return(parsedArray); +} + +static void *cdvjk_create_dictionary(CDVJKParseState *parseState, size_t startingObjectIndex) { + void *parsedDictionary = NULL; + + parseState->objectStack.index--; + + parsedDictionary = _CDVJKDictionaryCreate((id *)&parseState->objectStack.keys[startingObjectIndex], (NSUInteger *)&parseState->objectStack.cfHashes[startingObjectIndex], (id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); + + return(parsedDictionary); +} + +static void *cdvjk_parse_dictionary(CDVJKParseState *parseState) { + size_t startingObjectIndex = parseState->objectStack.index; + int dictState = CDVJKParseAcceptValueOrEnd, stopParsing = 0; + void *parsedDictionary = NULL; + + while(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { + if(CDVJK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(cdvjk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { cdvjk_error(parseState, @"Internal error: [dictionary] objectsIndex > %zu, resize failed? %@ line #%ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } + + size_t objectStackIndex = parseState->objectStack.index++; + parseState->objectStack.keys[objectStackIndex] = NULL; + parseState->objectStack.objects[objectStackIndex] = NULL; + void *key = NULL, *object = NULL; + + if(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)))) { + switch(parseState->token.type) { + case CDVJKTokenTypeString: + if(CDVJK_EXPECT_F((dictState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected string."); stopParsing = 1; break; } + if(CDVJK_EXPECT_F((key = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; } + else { + parseState->objectStack.keys[objectStackIndex] = key; + if(CDVJK_EXPECT_T(parseState->token.value.cacheItem != NULL)) { if(CDVJK_EXPECT_F(parseState->token.value.cacheItem->cfHash == 0UL)) { parseState->token.value.cacheItem->cfHash = CFHash(key); } parseState->objectStack.cfHashes[objectStackIndex] = parseState->token.value.cacheItem->cfHash; } + else { parseState->objectStack.cfHashes[objectStackIndex] = CFHash(key); } + } + break; + + case CDVJKTokenTypeObjectEnd: if((CDVJK_EXPECT_T(dictState & CDVJKParseAcceptEnd))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = cdvjk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break; + case CDVJKTokenTypeComma: if((CDVJK_EXPECT_T(dictState & CDVJKParseAcceptComma))) { dictState = CDVJKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; + + default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break; + } + } + + if(CDVJK_EXPECT_T(stopParsing == 0)) { + if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)) { if(CDVJK_EXPECT_F(parseState->token.type != CDVJKTokenTypeSeparator)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Expected ':'."); stopParsing = 1; } } + } + + if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0))) { + switch(parseState->token.type) { + case CDVJKTokenTypeNumber: + case CDVJKTokenTypeString: + case CDVJKTokenTypeTrue: + case CDVJKTokenTypeFalse: + case CDVJKTokenTypeNull: + case CDVJKTokenTypeArrayBegin: + case CDVJKTokenTypeObjectBegin: + if(CDVJK_EXPECT_F((dictState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } + if(CDVJK_EXPECT_F((object = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Object == NULL."); stopParsing = 1; break; } else { parseState->objectStack.objects[objectStackIndex] = object; dictState = CDVJKParseAcceptCommaOrEnd; } + break; + default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, dictState, @"a value", @"a comma", @"a '}'"); stopParsing = 1; break; + } + } + } + + if(CDVJK_EXPECT_F(parsedDictionary == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.keys[idx] != NULL) { CFRelease(parseState->objectStack.keys[idx]); parseState->objectStack.keys[idx] = NULL; } if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } +#if !defined(NS_BLOCK_ASSERTIONS) + else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } +#endif + + parseState->objectStack.index = startingObjectIndex; + return(parsedDictionary); +} + +static id json_parse_it(CDVJKParseState *parseState) { + id parsedObject = NULL; + int stopParsing = 0; + + while((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length))) { + if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0))) { + switch(parseState->token.type) { + case CDVJKTokenTypeArrayBegin: + case CDVJKTokenTypeObjectBegin: parsedObject = [(id)cdvjk_object_for_token(parseState) autorelease]; stopParsing = 1; break; + default: cdvjk_error(parseState, @"Expected either '[' or '{'."); stopParsing = 1; break; + } + } + } + + NSCParameterAssert((parseState->objectStack.index == 0) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); + + if((parsedObject == NULL) && (CDVJK_AT_STRING_PTR(parseState) == CDVJK_END_STRING_PTR(parseState))) { cdvjk_error(parseState, @"Reached the end of the buffer."); } + if(parsedObject == NULL) { cdvjk_error(parseState, @"Unable to parse CDVJSON."); } + + if((parsedObject != NULL) && (CDVJK_AT_STRING_PTR(parseState) < CDVJK_END_STRING_PTR(parseState))) { + cdvjk_parse_skip_whitespace(parseState); + if((parsedObject != NULL) && ((parseState->parseOptionFlags & CDVJKParseOptionPermitTextAfterValidJSON) == 0) && (CDVJK_AT_STRING_PTR(parseState) < CDVJK_END_STRING_PTR(parseState))) { + cdvjk_error(parseState, @"A valid CDVJSON object was parsed but there were additional non-white-space characters remaining."); + parsedObject = NULL; + } + } + + return(parsedObject); +} + +//////////// +#pragma mark - +#pragma mark Object cache + +// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1. +// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. +CDVJK_STATIC_INLINE void cdvjk_cache_age(CDVJKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U)); + parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U); + parseState->cache.age[parseState->cache.prng_lfsr & (parseState->cache.count - 1UL)] >>= 1; +} + +// The object cache is nothing more than a hash table with open addressing collision resolution that is bounded by CDVJK_CACHE_PROBES attempts. +// +// The hash table is a linear C array of CDVJKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket]. +// +// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4. +// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item). +// The reason for this is it allows us to perform saturated adds and subtractions and is branchless. +// The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits. +// +// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0. +// +// When an item is found in the cache, it's age is incremented. +// If a useable bucket hasn't been found, the current item (bucket) is aged along with two random items. +// +// If a value is not found in the cache, and no useable bucket has been found, that value is not added to the cache. + +static void *cdvjk_cachedObjects(CDVJKParseState *parseState) { + unsigned long bucket = parseState->token.value.hash & (parseState->cache.count - 1UL), setBucket = 0UL, useableBucket = 0UL, x = 0UL; + void *parsedAtom = NULL; + + if(CDVJK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && CDVJK_EXPECT_T(parseState->token.value.type == CDVJKValueTypeString)) { return(@""); } + + for(x = 0UL; x < CDVJK_CACHE_PROBES; x++) { + if(CDVJK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; } + + if((CDVJK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (CDVJK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) { + parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U; + parseState->token.value.cacheItem = &parseState->cache.items[bucket]; + NSCParameterAssert(parseState->cache.items[bucket].object != NULL); + return((void *)CFRetain(parseState->cache.items[bucket].object)); + } else { + if(CDVJK_EXPECT_F(setBucket == 0UL) && CDVJK_EXPECT_F(parseState->cache.age[bucket] == 0U)) { setBucket = 1UL; useableBucket = bucket; } + if(CDVJK_EXPECT_F(setBucket == 0UL)) { parseState->cache.age[bucket] >>= 1; cdvjk_cache_age(parseState); cdvjk_cache_age(parseState); } + // This is the open addressing function. The values length and type are used as a form of "double hashing" to distribute values with the same effective value hash across different object cache buckets. + // The values type is a prime number that is relatively coprime to the other primes in the set of value types and the number of hash table buckets. + bucket = (parseState->token.value.hash + (parseState->token.value.ptrRange.length * (x + 1UL)) + (parseState->token.value.type * (x + 1UL)) + (3UL * (x + 1UL))) & (parseState->cache.count - 1UL); + } + } + + switch(parseState->token.value.type) { + case CDVJKValueTypeString: parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break; + case CDVJKValueTypeLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue); break; + case CDVJKValueTypeUnsignedLongLong: + if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); } + else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); } + break; + case CDVJKValueTypeDouble: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType, &parseState->token.value.number.doubleValue); break; + default: cdvjk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + + if(CDVJK_EXPECT_T(setBucket) && (CDVJK_EXPECT_T(parsedAtom != NULL))) { + bucket = useableBucket; + if(CDVJK_EXPECT_T((parseState->cache.items[bucket].object != NULL))) { CFRelease(parseState->cache.items[bucket].object); parseState->cache.items[bucket].object = NULL; } + + if(CDVJK_EXPECT_T((parseState->cache.items[bucket].bytes = (unsigned char *)reallocf(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.length)) != NULL)) { + memcpy(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length); + parseState->cache.items[bucket].object = (void *)CFRetain(parsedAtom); + parseState->cache.items[bucket].hash = parseState->token.value.hash; + parseState->cache.items[bucket].cfHash = 0UL; + parseState->cache.items[bucket].size = parseState->token.value.ptrRange.length; + parseState->cache.items[bucket].type = parseState->token.value.type; + parseState->token.value.cacheItem = &parseState->cache.items[bucket]; + parseState->cache.age[bucket] = CDVJK_INIT_CACHE_AGE; + } else { // The realloc failed, so clear the appropriate fields. + parseState->cache.items[bucket].hash = 0UL; + parseState->cache.items[bucket].cfHash = 0UL; + parseState->cache.items[bucket].size = 0UL; + parseState->cache.items[bucket].type = 0UL; + } + } + + return(parsedAtom); +} + + +static void *cdvjk_object_for_token(CDVJKParseState *parseState) { + void *parsedAtom = NULL; + + parseState->token.value.cacheItem = NULL; + switch(parseState->token.type) { + case CDVJKTokenTypeString: parsedAtom = cdvjk_cachedObjects(parseState); break; + case CDVJKTokenTypeNumber: parsedAtom = cdvjk_cachedObjects(parseState); break; + case CDVJKTokenTypeObjectBegin: parsedAtom = cdvjk_parse_dictionary(parseState); break; + case CDVJKTokenTypeArrayBegin: parsedAtom = cdvjk_parse_array(parseState); break; + case CDVJKTokenTypeTrue: parsedAtom = (void *)kCFBooleanTrue; break; + case CDVJKTokenTypeFalse: parsedAtom = (void *)kCFBooleanFalse; break; + case CDVJKTokenTypeNull: parsedAtom = (void *)kCFNull; break; + default: cdvjk_error(parseState, @"Internal error: Unknown token type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + + return(parsedAtom); +} + +#pragma mark - +@implementation CDVJSONDecoder + ++ (id)decoder +{ + return([self decoderWithParseOptions:CDVJKParseOptionStrict]); +} + ++ (id)decoderWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + return([[[self alloc] initWithParseOptions:parseOptionFlags] autorelease]); +} + +- (id)init +{ + return([self initWithParseOptions:CDVJKParseOptionStrict]); +} + +- (id)initWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + if((self = [super init]) == NULL) { return(NULL); } + + if(parseOptionFlags & ~CDVJKParseOptionValidFlags) { [self autorelease]; [NSException raise:NSInvalidArgumentException format:@"Invalid parse options."]; } + + if((parseState = (CDVJKParseState *)calloc(1UL, sizeof(CDVJKParseState))) == NULL) { goto errorExit; } + + parseState->parseOptionFlags = parseOptionFlags; + + parseState->token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL; + parseState->objectStack.roundSizeUpToMultipleOf = 2048UL; + + parseState->objCImpCache.NSNumberClass = _CDVjk_NSNumberClass; + parseState->objCImpCache.NSNumberAlloc = _CDVjk_NSNumberAllocImp; + parseState->objCImpCache.NSNumberInitWithUnsignedLongLong = _CDVjk_NSNumberInitWithUnsignedLongLongImp; + + parseState->cache.prng_lfsr = 1U; + parseState->cache.count = CDVJK_CACHE_SLOTS; + if((parseState->cache.items = (CDVJKTokenCacheItem *)calloc(1UL, sizeof(CDVJKTokenCacheItem) * parseState->cache.count)) == NULL) { goto errorExit; } + + return(self); + + errorExit: + if(self) { [self autorelease]; self = NULL; } + return(NULL); +} + +// This is here primarily to support the NSString and NSData convenience functions so the autoreleased CDVJSONDecoder can release most of its resources before the pool pops. +static void _JSONDecoderCleanup(CDVJSONDecoder *decoder) { + if((decoder != NULL) && (decoder->parseState != NULL)) { + cdvjk_managedBuffer_release(&decoder->parseState->token.tokenBuffer); + cdvjk_objectStack_release(&decoder->parseState->objectStack); + + [decoder clearCache]; + if(decoder->parseState->cache.items != NULL) { free(decoder->parseState->cache.items); decoder->parseState->cache.items = NULL; } + + free(decoder->parseState); decoder->parseState = NULL; + } +} + +- (void)dealloc +{ + _JSONDecoderCleanup(self); + [super dealloc]; +} + +- (void)clearCache +{ + if(CDVJK_EXPECT_T(parseState != NULL)) { + if(CDVJK_EXPECT_T(parseState->cache.items != NULL)) { + size_t idx = 0UL; + for(idx = 0UL; idx < parseState->cache.count; idx++) { + if(CDVJK_EXPECT_T(parseState->cache.items[idx].object != NULL)) { CFRelease(parseState->cache.items[idx].object); parseState->cache.items[idx].object = NULL; } + if(CDVJK_EXPECT_T(parseState->cache.items[idx].bytes != NULL)) { free(parseState->cache.items[idx].bytes); parseState->cache.items[idx].bytes = NULL; } + memset(&parseState->cache.items[idx], 0, sizeof(CDVJKTokenCacheItem)); + parseState->cache.age[idx] = 0U; + } + } + } +} + +// This needs to be completely rewritten. +static id _CDVJKParseUTF8String(CDVJKParseState *parseState, BOOL mutableCollections, const unsigned char *string, size_t length, NSError **error) { + NSCParameterAssert((parseState != NULL) && (string != NULL) && (parseState->cache.prng_lfsr != 0U)); + parseState->stringBuffer.bytes.ptr = string; + parseState->stringBuffer.bytes.length = length; + parseState->atIndex = 0UL; + parseState->lineNumber = 1UL; + parseState->lineStartIndex = 0UL; + parseState->prev_atIndex = 0UL; + parseState->prev_lineNumber = 1UL; + parseState->prev_lineStartIndex = 0UL; + parseState->error = NULL; + parseState->errorIsPrev = 0; + parseState->mutableCollections = (mutableCollections == NO) ? NO : YES; + + unsigned char stackTokenBuffer[CDVJK_TOKENBUFFER_SIZE] CDVJK_ALIGNED(64); + cdvjk_managedBuffer_setToStackBuffer(&parseState->token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer)); + + void *stackObjects [CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); + void *stackKeys [CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); + CFHashCode stackCFHashes[CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); + cdvjk_objectStack_setToStackBuffer(&parseState->objectStack, stackObjects, stackKeys, stackCFHashes, CDVJK_STACK_OBJS); + + id parsedJSON = json_parse_it(parseState); + + if((error != NULL) && (parseState->error != NULL)) { *error = parseState->error; } + + cdvjk_managedBuffer_release(&parseState->token.tokenBuffer); + cdvjk_objectStack_release(&parseState->objectStack); + + parseState->stringBuffer.bytes.ptr = NULL; + parseState->stringBuffer.bytes.length = 0UL; + parseState->atIndex = 0UL; + parseState->lineNumber = 1UL; + parseState->lineStartIndex = 0UL; + parseState->prev_atIndex = 0UL; + parseState->prev_lineNumber = 1UL; + parseState->prev_lineStartIndex = 0UL; + parseState->error = NULL; + parseState->errorIsPrev = 0; + parseState->mutableCollections = NO; + + return(parsedJSON); +} + +//////////// +#pragma mark Deprecated as of v1.4 +//////////// + +// Deprecated in CDVJSONKit v1.4. Use objectWithUTF8String:length: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length +{ + return([self objectWithUTF8String:string length:length error:NULL]); +} + +// Deprecated in CDVJSONKit v1.4. Use objectWithUTF8String:length:error: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error +{ + return([self objectWithUTF8String:string length:length error:error]); +} + +// Deprecated in CDVJSONKit v1.4. Use objectWithData: instead. +- (id)parseJSONData:(NSData *)jsonData +{ + return([self objectWithData:jsonData error:NULL]); +} + +// Deprecated in CDVJSONKit v1.4. Use objectWithData:error: instead. +- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error +{ + return([self objectWithData:jsonData error:error]); +} + +//////////// +#pragma mark Methods that return immutable collection objects +//////////// + +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length +{ + return([self objectWithUTF8String:string length:length error:NULL]); +} + +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error +{ + if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } + if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } + + return(_CDVJKParseUTF8String(parseState, NO, string, (size_t)length, error)); +} + +- (id)objectWithData:(NSData *)jsonData +{ + return([self objectWithData:jsonData error:NULL]); +} + +- (id)objectWithData:(NSData *)jsonData error:(NSError **)error +{ + if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } + return([self objectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); +} + +//////////// +#pragma mark Methods that return mutable collection objects +//////////// + +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length +{ + return([self mutableObjectWithUTF8String:string length:length error:NULL]); +} + +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error +{ + if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } + if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } + + return(_CDVJKParseUTF8String(parseState, YES, string, (size_t)length, error)); +} + +- (id)mutableObjectWithData:(NSData *)jsonData +{ + return([self mutableObjectWithData:jsonData error:NULL]); +} + +- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error +{ + if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } + return([self mutableObjectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); +} + +@end + +/* + The NSString and NSData convenience methods need a little bit of explanation. + + Prior to CDVJSONKit v1.4, the NSString -objectFromJSONStringWithParseOptions:error: method looked like + + const unsigned char *utf8String = (const unsigned char *)[self UTF8String]; + if(utf8String == NULL) { return(NULL); } + size_t utf8Length = strlen((const char *)utf8String); + return([[JSONDecoder decoderWithParseOptions:parseOptionFlags] parseUTF8String:utf8String length:utf8Length error:error]); + + This changed with v1.4 to a more complicated method. The reason for this is to keep the amount of memory that is + allocated, but not yet freed because it is dependent on the autorelease pool to pop before it can be reclaimed. + + In the simpler v1.3 code, this included all the bytes used to store the -UTF8String along with the CDVJSONDecoder and all its overhead. + + Now we use an autoreleased CFMutableData that is sized to the UTF8 length of the NSString in question and is used to hold the UTF8 + conversion of said string. + + Once parsed, the CFMutableData has its length set to 0. This should, hopefully, allow the CFMutableData to realloc and/or free + the buffer. + + Another change made was a slight modification to CDVJSONDecoder so that most of the cleanup work that was done in -dealloc was moved + to a private, internal function. These convenience routines keep the pointer to the autoreleased CDVJSONDecoder and calls + _JSONDecoderCleanup() to early release the decoders resources since we already know that particular decoder is not going to be used + again. + + If everything goes smoothly, this will most likely result in perhaps a few hundred bytes that are allocated but waiting for the + autorelease pool to pop. This is compared to the thousands and easily hundreds of thousands of bytes that would have been in + autorelease limbo. It's more complicated for us, but a win for the user. + + Autorelease objects are used in case things don't go smoothly. By having them autoreleased, we effectively guarantee that our + requirement to -release the object is always met, not matter what goes wrong. The downside is having a an object or two in + autorelease limbo, but we've done our best to minimize that impact, so it all balances out. + */ + +@implementation NSString (CDVJSONKitDeserializing) + +static id _NSStringObjectFromJSONString(NSString *jsonString, CDVJKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection) { + id returnObject = NULL; + CFMutableDataRef mutableData = NULL; + CDVJSONDecoder *decoder = NULL; + + CFIndex stringLength = CFStringGetLength((CFStringRef)jsonString); + NSUInteger stringUTF8Length = [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if((mutableData = (CFMutableDataRef)[(id)CFDataCreateMutable(NULL, (NSUInteger)stringUTF8Length) autorelease]) != NULL) { + UInt8 *utf8String = CFDataGetMutableBytePtr(mutableData); + CFIndex usedBytes = 0L, convertedCount = 0L; + + convertedCount = CFStringGetBytes((CFStringRef)jsonString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, utf8String, (NSUInteger)stringUTF8Length, &usedBytes); + if(CDVJK_EXPECT_F(convertedCount != stringLength) || CDVJK_EXPECT_F(usedBytes < 0L)) { if(error != NULL) { *error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo:[NSDictionary dictionaryWithObject:@"An error occurred converting the contents of a NSString to UTF8." forKey:NSLocalizedDescriptionKey]]; } goto exitNow; } + + if(mutableCollection == NO) { returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } + else { returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } + } + +exitNow: + if(mutableData != NULL) { CFDataSetLength(mutableData, 0L); } + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + +- (id)cdvjk_objectFromJSONString +{ + return([self cdvjk_objectFromJSONStringWithParseOptions:CDVJKParseOptionStrict error:NULL]); +} + +- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + return([self cdvjk_objectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, NO)); +} + + +- (id)cdvjk_mutableObjectFromJSONString +{ + return([self cdvjk_mutableObjectFromJSONStringWithParseOptions:CDVJKParseOptionStrict error:NULL]); +} + +- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + return([self cdvjk_mutableObjectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, YES)); +} + +@end + +@implementation NSData (CDVJSONKitDeserializing) + +- (id)cdvjk_objectFromJSONData +{ + return([self cdvjk_objectFromJSONDataWithParseOptions:CDVJKParseOptionStrict error:NULL]); +} + +- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + return([self cdvjk_objectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + CDVJSONDecoder *decoder = NULL; + id returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithData:self error:error]; + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + +- (id)cdvjk_mutableObjectFromJSONData +{ + return([self cdvjk_mutableObjectFromJSONDataWithParseOptions:CDVJKParseOptionStrict error:NULL]); +} + +- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags +{ + return([self cdvjk_mutableObjectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + CDVJSONDecoder *decoder = NULL; + id returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithData:self error:error]; + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + + +@end + +//////////// +#pragma mark - +#pragma mark Encoding / deserializing functions + +static void cdvjk_encode_error(CDVJKEncodeState *encodeState, NSString *format, ...) { + NSCParameterAssert((encodeState != NULL) && (format != NULL)); + + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + + if(encodeState->error == NULL) { + encodeState->error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + formatString, NSLocalizedDescriptionKey, + NULL]]; + } +} + +CDVJK_STATIC_INLINE void cdvjk_encode_updateCache(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object) { + NSCParameterAssert(encodeState != NULL); + if(CDVJK_EXPECT_T(cacheSlot != NULL)) { + NSCParameterAssert((object != NULL) && (startingAtIndex <= encodeState->atIndex)); + cacheSlot->object = object; + cacheSlot->offset = startingAtIndex; + cacheSlot->length = (size_t)(encodeState->atIndex - startingAtIndex); + } +} + +static int cdvjk_encode_printf(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...) { + va_list varArgsList, varArgsListCopy; + va_start(varArgsList, format); + va_copy(varArgsListCopy, varArgsList); + + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); + + ssize_t formattedStringLength = 0L; + int returnValue = 0; + + if(CDVJK_EXPECT_T((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsList)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { + NSCParameterAssert(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)); + if(CDVJK_EXPECT_F(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (formattedStringLength * 2UL)+ 4096UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); returnValue = 1; goto exitNow; } + if(CDVJK_EXPECT_F((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsListCopy)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { cdvjk_encode_error(encodeState, @"vsnprintf failed unexpectedly."); returnValue = 1; goto exitNow; } + } + +exitNow: + va_end(varArgsList); + va_end(varArgsListCopy); + if(CDVJK_EXPECT_T(returnValue == 0)) { encodeState->atIndex += formattedStringLength; cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); } + return(returnValue); +} + +static int cdvjk_encode_write(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); + if(CDVJK_EXPECT_F(((encodeState->atIndex + strlen(format) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + strlen(format) + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + size_t formatIdx = 0UL; + for(formatIdx = 0UL; format[formatIdx] != 0; formatIdx++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[formatIdx]; } + cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); + return(0); +} + +static int cdvjk_encode_writePrettyPrintWhiteSpace(CDVJKEncodeState *encodeState) { + NSCParameterAssert((encodeState != NULL) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) != 0UL)); + if(CDVJK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_T(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\n'; + size_t depthWhiteSpace = 0UL; + for(depthWhiteSpace = 0UL; depthWhiteSpace < (encodeState->depth * 2UL); depthWhiteSpace++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } + return(0); +} + +static int cdvjk_encode_write1slow(CDVJKEncodeState *encodeState, ssize_t depthChange, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (format != NULL) && ((depthChange >= -1L) && (depthChange <= 1L)) && ((encodeState->depth == 0UL) ? (depthChange >= 0L) : 1) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) != 0UL)); + if(CDVJK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + encodeState->depth += depthChange; + if(CDVJK_EXPECT_T(format[0] == ':')) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } + else { + if(CDVJK_EXPECT_F(depthChange == -1L)) { if(CDVJK_EXPECT_F(cdvjk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; + if(CDVJK_EXPECT_T(depthChange != -1L)) { if(CDVJK_EXPECT_F(cdvjk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } + } + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + return(0); +} + +static int cdvjk_encode_write1fast(CDVJKEncodeState *encodeState, ssize_t depthChange CDVJK_UNUSED_ARG, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) == 0UL)); + if(CDVJK_EXPECT_T((encodeState->atIndex + 4UL) < encodeState->stringBuffer.bytes.length)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; } + else { return(cdvjk_encode_write(encodeState, NULL, 0UL, NULL, format)); } + return(0); +} + +static int cdvjk_encode_writen(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex)); + if(CDVJK_EXPECT_F((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < (length + 4UL))) { if(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL + length) == NULL) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } } + memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, format, length); + encodeState->atIndex += length; + cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); + return(0); +} + +CDVJK_STATIC_INLINE CDVJKHash cdvjk_encode_object_hash(void *objectPtr) { + return( ( (((CDVJKHash)objectPtr) >> 21) ^ (((CDVJKHash)objectPtr) >> 9) ) + (((CDVJKHash)objectPtr) >> 4) ); +} + +static int cdvjk_encode_add_atom_to_buffer(CDVJKEncodeState *encodeState, void *objectPtr) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL)); + + id object = (id)objectPtr, encodeCacheObject = object; + int isClass = CDVJKClassUnknown; + size_t startingAtIndex = encodeState->atIndex; + + CDVJKHash objectHash = cdvjk_encode_object_hash(objectPtr); + CDVJKEncodeCache *cacheSlot = &encodeState->cache[objectHash % CDVJK_ENCODE_CACHE_SLOTS]; + + if(CDVJK_EXPECT_T(cacheSlot->object == object)) { + NSCParameterAssert((cacheSlot->object != NULL) && + (cacheSlot->offset < encodeState->atIndex) && ((cacheSlot->offset + cacheSlot->length) < encodeState->atIndex) && + (cacheSlot->offset < encodeState->stringBuffer.bytes.length) && ((cacheSlot->offset + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length))); + if(CDVJK_EXPECT_F(((encodeState->atIndex + cacheSlot->length + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + cacheSlot->length + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + NSCParameterAssert(((encodeState->atIndex + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->atIndex))); + memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, encodeState->stringBuffer.bytes.ptr + cacheSlot->offset, cacheSlot->length); + encodeState->atIndex += cacheSlot->length; + return(0); + } + + // When we encounter a class that we do not handle, and we have either a delegate or block that the user supplied to format unsupported classes, + // we "re-run" the object check. However, we re-run the object check exactly ONCE. If the user supplies an object that isn't one of the + // supported classes, we fail the second time (i.e., double fault error). + BOOL rerunningAfterClassFormatter = NO; + rerunAfterClassFormatter:; + + // XXX XXX XXX XXX + // + // We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep. + // + // It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class". + // + // This is euphemistically called "tagged pointers". There are a number of highly technical problems with this, most involving long passages from + // the C standard(s). In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are + // fundamentally Wrong and Broken, and should have never been implemented. Assuming those points are glossed over, because the change is very clearly + // breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent + // causes code that used to work just fine to suddenly break without warning. + // + // In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the + // "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures + // `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both. (note: this is informal, not meant to be + // normative or pedantically correct). + // + // In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler + // is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below. + // + // Fan-fucking-tastic. + // + // Why not just use `object_getClass()`? Because `object->isa` reduces to (typically) a *single* instruction. Calling `object_getClass()` requires + // that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction. + // Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen + // instructions in its prolog. One instruction compared to dozens, maybe a hundred instructions. + // + // Yes, that's one to two orders of magnitude difference. Which is compelling in its own right. When going for performance, you're often happy with + // gains in the two to three percent range. + // + // XXX XXX XXX XXX + + BOOL workAroundMacOSXABIBreakingBug = NO; + if(CDVJK_EXPECT_F(((NSUInteger)object) & 0x1)) { workAroundMacOSXABIBreakingBug = YES; goto slowClassLookup; } + + if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.stringClass)) { isClass = CDVJKClassString; } + else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.numberClass)) { isClass = CDVJKClassNumber; } + else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.dictionaryClass)) { isClass = CDVJKClassDictionary; } + else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.arrayClass)) { isClass = CDVJKClassArray; } + else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.nullClass)) { isClass = CDVJKClassNull; } + else { + slowClassLookup: + if(CDVJK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = object_getClass(object); } isClass = CDVJKClassString; } + else if(CDVJK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = object_getClass(object); } isClass = CDVJKClassNumber; } + else if(CDVJK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = object_getClass(object); } isClass = CDVJKClassDictionary; } + else if(CDVJK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = object_getClass(object); } isClass = CDVJKClassArray; } + else if(CDVJK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = object_getClass(object); } isClass = CDVJKClassNull; } + else { + if((rerunningAfterClassFormatter == NO) && ( +#ifdef __BLOCKS__ + ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != NULL)) || +#endif + ((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; } + + if(rerunningAfterClassFormatter == NO) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); } + else { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); } + } + } + + // This is here for the benefit of the optimizer. It allows the optimizer to do loop invariant code motion for the CDVJKClassArray + // and CDVJKClassDictionary cases when printing simple, single characters via cdvjk_encode_write(), which is actually a macro: + // #define cdvjk_encode_write1(es, dc, f) (_jk_encode_prettyPrint ? cdvjk_encode_write1slow(es, dc, f) : cdvjk_encode_write1fast(es, dc, f)) + int _jk_encode_prettyPrint = CDVJK_EXPECT_T((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) == 0) ? 0 : 1; + + switch(isClass) { + case CDVJKClassString: + { + { + const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman); + if(cStringPtr != NULL) { + const unsigned char *utf8String = cStringPtr; + size_t utf8Idx = 0UL; + + CFIndex stringLength = CFStringGetLength((CFStringRef)object); + if(CDVJK_EXPECT_F(((encodeState->atIndex + (stringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (stringLength * 2UL) + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + for(utf8Idx = 0UL; utf8String[utf8Idx] != 0U; utf8Idx++) { + NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + if(CDVJK_EXPECT_F(utf8String[utf8Idx] >= 0x80U)) { encodeState->atIndex = startingAtIndex; goto slowUTF8Path; } + if(CDVJK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { + switch(utf8String[utf8Idx]) { + case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; + case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; + case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; + case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; + case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; + default: if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; + } + } else { + if(CDVJK_EXPECT_F(utf8String[utf8Idx] == '\"') || CDVJK_EXPECT_F(utf8String[utf8Idx] == '\\') || (CDVJK_EXPECT_F(encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeForwardSlashes) && CDVJK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; + } + } + NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); + if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); + return(0); + } + } + + slowUTF8Path: + { + CFIndex stringLength = CFStringGetLength((CFStringRef)object); + CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L; + + if(CDVJK_EXPECT_F((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + CFIndex usedBytes = 0L, convertedCount = 0L; + convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes); + if(CDVJK_EXPECT_F(convertedCount != stringLength) || CDVJK_EXPECT_F(usedBytes < 0L)) { cdvjk_encode_error(encodeState, @"An error occurred converting the contents of a NSString to UTF8."); return(1); } + + if(CDVJK_EXPECT_F((encodeState->atIndex + (maxStringUTF8Length * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (maxStringUTF8Length * 2UL) + 1024UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr; + + size_t utf8Idx = 0UL; + if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + for(utf8Idx = 0UL; utf8Idx < (size_t)usedBytes; utf8Idx++) { + NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + NSCParameterAssert((CFIndex)utf8Idx < usedBytes); + if(CDVJK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { + switch(utf8String[utf8Idx]) { + case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; + case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; + case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; + case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; + case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; + default: if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; + } + } else { + if(CDVJK_EXPECT_F(utf8String[utf8Idx] >= 0x80U) && (encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeUnicode)) { + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + CDV_ConversionResult result; + + if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(&utf8String[utf8Idx], &utf8String[usedBytes], (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { cdvjk_encode_error(encodeState, @"Error converting UTF8."); return(1); } + else { + utf8Idx = (nextValidCharacter - utf8String) - 1UL; + if(CDVJK_EXPECT_T(u32ch <= 0xffffU)) { if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", u32ch))) { return(1); } } + else { if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x\\u%4.4x", (0xd7c0U + (u32ch >> 10)), (0xdc00U + (u32ch & 0x3ffU))))) { return(1); } } + } + } else { + if(CDVJK_EXPECT_F(utf8String[utf8Idx] == '\"') || CDVJK_EXPECT_F(utf8String[utf8Idx] == '\\') || (CDVJK_EXPECT_F(encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeForwardSlashes) && CDVJK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; + } + } + } + NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); + if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); + return(0); + } + } + break; + + case CDVJKClassNumber: + { + if(object == (id)kCFBooleanTrue) { return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "true", 4UL)); } + else if(object == (id)kCFBooleanFalse) { return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "false", 5UL)); } + + const char *objCType = [object objCType]; + char anum[256], *aptr = &anum[255]; + int isNegative = 0; + unsigned long long ullv; + long long llv; + + if(CDVJK_EXPECT_F(objCType == NULL) || CDVJK_EXPECT_F(objCType[0] == 0) || CDVJK_EXPECT_F(objCType[1] != 0)) { cdvjk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%s'", (objCType == NULL) ? "<NULL>" : objCType); return(1); } + + switch(objCType[0]) { + case 'c': case 'i': case 's': case 'l': case 'q': + if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv))) { + if(llv < 0LL) { ullv = -llv; isNegative = 1; } else { ullv = llv; isNegative = 0; } + goto convertNumber; + } else { cdvjk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } + break; + case 'C': case 'I': case 'S': case 'L': case 'Q': case 'B': + if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv))) { + convertNumber: + if(CDVJK_EXPECT_F(ullv < 10ULL)) { *--aptr = ullv + '0'; } else { while(CDVJK_EXPECT_T(ullv > 0ULL)) { *--aptr = (ullv % 10ULL) + '0'; ullv /= 10ULL; NSCParameterAssert(aptr > anum); } } + if(isNegative) { *--aptr = '-'; } + NSCParameterAssert(aptr > anum); + return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, aptr, &anum[255] - aptr)); + } else { cdvjk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } + break; + case 'f': case 'd': + { + double dv; + if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv))) { + if(CDVJK_EXPECT_F(!isfinite(dv))) { cdvjk_encode_error(encodeState, @"Floating point values must be finite. CDVJSON does not support NaN or Infinity."); return(1); } + return(cdvjk_encode_printf(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "%.17g", dv)); + } else { cdvjk_encode_error(encodeState, @"Unable to get floating point value from number object."); return(1); } + } + break; + default: cdvjk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%c' / 0x%2.2x", objCType[0], objCType[0]); return(1); break; + } + } + break; + + case CDVJKClassArray: + { + int printComma = 0; + CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L; + if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 1L, "["))) { return(1); } + if(CDVJK_EXPECT_F(arrayCount > 1020L)) { + for(id arrayObject in object) { if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, arrayObject))) { return(1); } } + } else { + void *objects[1024]; + CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects); + for(idx = 0L; idx < arrayCount; idx++) { if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } } + } + return(cdvjk_encode_write1(encodeState, -1L, "]")); + } + break; + + case CDVJKClassDictionary: + { + int printComma = 0; + CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L; + id enumerateObject = CDVJK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; + + if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 1L, "{"))) { return(1); } + if(CDVJK_EXPECT_F(_jk_encode_prettyPrint) || CDVJK_EXPECT_F(dictionaryCount > 1020L)) { + for(id keyObject in enumerateObject) { + if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } + printComma = 1; + if(CDVJK_EXPECT_F((object_getClass(keyObject) != encodeState->fastClassLookup.stringClass)) && CDVJK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { cdvjk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } + } + } else { + void *keys[1024], *objects[1024]; + CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects); + for(idx = 0L; idx < dictionaryCount; idx++) { + if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } + printComma = 1; + if(CDVJK_EXPECT_F(object_getClass(((id)keys[idx])) != encodeState->fastClassLookup.stringClass) && CDVJK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { cdvjk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } + } + } + return(cdvjk_encode_write1(encodeState, -1L, "}")); + } + break; + + case CDVJKClassNull: return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "null", 4UL)); break; + + default: cdvjk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([object class])); return(1); break; + } + + return(0); +} + + +@implementation CDVJKSerializer + ++ (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([[[[self alloc] init] autorelease] serializeObject:object options:optionFlags encodeOption:encodeOption block:block delegate:delegate selector:selector error:error]); +} + +- (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ +#ifndef __BLOCKS__ +#pragma unused(block) +#endif + NSParameterAssert((object != NULL) && (encodeState == NULL) && ((delegate != NULL) ? (block == NULL) : 1) && ((block != NULL) ? (delegate == NULL) : 1) && + (((encodeOption & CDVJKEncodeOptionCollectionObj) != 0UL) ? (((encodeOption & CDVJKEncodeOptionStringObj) == 0UL) && ((encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) : 1) && + (((encodeOption & CDVJKEncodeOptionStringObj) != 0UL) ? ((encodeOption & CDVJKEncodeOptionCollectionObj) == 0UL) : 1)); + + id returnObject = NULL; + + if(encodeState != NULL) { [self releaseState]; } + if((encodeState = (struct CDVJKEncodeState *)calloc(1UL, sizeof(CDVJKEncodeState))) == NULL) { [NSException raise:NSMallocException format:@"Unable to allocate state structure."]; return(NULL); } + + if((error != NULL) && (*error != NULL)) { *error = NULL; } + + if(delegate != NULL) { + if(selector == NULL) { [NSException raise:NSInvalidArgumentException format:@"The delegate argument is not NULL, but the selector argument is NULL."]; } + if([delegate respondsToSelector:selector] == NO) { [NSException raise:NSInvalidArgumentException format:@"The serializeUnsupportedClassesUsingDelegate: delegate does not respond to the selector argument."]; } + encodeState->classFormatterDelegate = delegate; + encodeState->classFormatterSelector = selector; + encodeState->classFormatterIMP = (CDVJKClassFormatterIMP)[delegate methodForSelector:selector]; + NSCParameterAssert(encodeState->classFormatterIMP != NULL); + } + +#ifdef __BLOCKS__ + encodeState->classFormatterBlock = block; +#endif + encodeState->serializeOptionFlags = optionFlags; + encodeState->encodeOption = encodeOption; + encodeState->stringBuffer.roundSizeUpToMultipleOf = (1024UL * 32UL); + encodeState->utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL; + + unsigned char stackJSONBuffer[CDVJK_JSONBUFFER_SIZE] CDVJK_ALIGNED(64); + cdvjk_managedBuffer_setToStackBuffer(&encodeState->stringBuffer, stackJSONBuffer, sizeof(stackJSONBuffer)); + + unsigned char stackUTF8Buffer[CDVJK_UTF8BUFFER_SIZE] CDVJK_ALIGNED(64); + cdvjk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); + + if(((encodeOption & CDVJKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & CDVJKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } + + if(cdvjk_encode_add_atom_to_buffer(encodeState, object) == 0) { + BOOL stackBuffer = ((encodeState->stringBuffer.flags & CDVJKManagedBufferMustFree) == 0UL) ? YES : NO; + + if((encodeState->atIndex < 2UL)) + if((stackBuffer == NO) && ((encodeState->stringBuffer.bytes.ptr = (unsigned char *)reallocf(encodeState->stringBuffer.bytes.ptr, encodeState->atIndex + 16UL)) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to realloc buffer"); goto errorExit; } + + switch((encodeOption & CDVJKEncodeOptionAsTypeMask)) { + case CDVJKEncodeOptionAsData: + if(stackBuffer == YES) { if((returnObject = [(id)CFDataCreate( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSData object"); } } + else { if((returnObject = [(id)CFDataCreateWithBytesNoCopy( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, NULL) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSData object"); } } + break; + + case CDVJKEncodeOptionAsString: + if(stackBuffer == YES) { if((returnObject = [(id)CFStringCreateWithBytes( NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSString object"); } } + else { if((returnObject = [(id)CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO, NULL) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSString object"); } } + break; + + default: cdvjk_encode_error(encodeState, @"Unknown encode as type."); break; + } + + if((returnObject != NULL) && (stackBuffer == NO)) { encodeState->stringBuffer.flags &= ~CDVJKManagedBufferMustFree; encodeState->stringBuffer.bytes.ptr = NULL; encodeState->stringBuffer.bytes.length = 0UL; } + } + +errorExit: + if((encodeState != NULL) && (error != NULL) && (encodeState->error != NULL)) { *error = encodeState->error; encodeState->error = NULL; } + [self releaseState]; + + return(returnObject); +} + +- (void)releaseState +{ + if(encodeState != NULL) { + cdvjk_managedBuffer_release(&encodeState->stringBuffer); + cdvjk_managedBuffer_release(&encodeState->utf8ConversionBuffer); + free(encodeState); encodeState = NULL; + } +} + +- (void)dealloc +{ + [self releaseState]; + [super dealloc]; +} + +@end + +@implementation NSString (CDVJSONKitSerializing) + +//////////// +#pragma mark Methods for serializing a single NSString. +//////////// + +// Useful for those who need to serialize just a NSString. Otherwise you would have to do something like [NSArray arrayWithObject:stringToBeJSONSerialized], serializing the array, and then chopping of the extra ^\[.*\]$ square brackets. + +// NSData returning methods... + +- (NSData *)cdvjk_JSONData +{ + return([self cdvjk_JSONDataWithOptions:CDVJKSerializeOptionNone includeQuotes:YES error:NULL]); +} + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | ((includeQuotes == NO) ? CDVJKEncodeOptionStringObjTrimQuotes : 0UL) | CDVJKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +// NSString returning methods... + +- (NSString *)cdvjk_JSONString +{ + return([self cdvjk_JSONStringWithOptions:CDVJKSerializeOptionNone includeQuotes:YES error:NULL]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | ((includeQuotes == NO) ? CDVJKEncodeOptionStringObjTrimQuotes : 0UL) | CDVJKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +@end + +@implementation NSArray (CDVJSONKitSerializing) + +// NSData returning methods... + +- (NSData *)cdvjk_JSONData +{ + return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +// NSString returning methods... + +- (NSString *)cdvjk_JSONString +{ + return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +@end + +@implementation NSDictionary (CDVJSONKitSerializing) + +// NSData returning methods... + +- (NSData *)cdvjk_JSONData +{ + return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +// NSString returning methods... + +- (NSString *)cdvjk_JSONString +{ + return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +@end + + +#ifdef __BLOCKS__ + +@implementation NSArray (CDVJSONKitSerializingBlockAdditions) + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +@end + +@implementation NSDictionary (CDVJSONKitSerializingBlockAdditions) + +- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +@end + +#endif // __BLOCKS__ + diff --git a/iPhone/CordovaLib/Classes/NSArray+Comparisons.h b/iPhone/CordovaLib/Classes/NSArray+Comparisons.h new file mode 100755 index 0000000..cd02b71 --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSArray+Comparisons.h @@ -0,0 +1,26 @@ +/* + 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 NSArray (Comparisons) + +- (id)objectAtIndex:(NSUInteger)index withDefault:(id)aDefault; + +@end diff --git a/iPhone/CordovaLib/Classes/NSArray+Comparisons.m b/iPhone/CordovaLib/Classes/NSArray+Comparisons.m new file mode 100755 index 0000000..485e3c8 --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSArray+Comparisons.m @@ -0,0 +1,41 @@ +/* + 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 "NSArray+Comparisons.h" + +@implementation NSArray (Comparisons) + +- (id)objectAtIndex:(NSUInteger)index withDefault:(id)aDefault +{ + id obj = nil; + + @try { + obj = [self objectAtIndex:index]; + if ((obj == [NSNull null]) || (obj == nil)) { + return aDefault; + } + } + @catch(NSException* exception) { + NSLog(@"Exception - Name: %@ Reason: %@", [exception name], [exception reason]); + } + + return obj; +} + +@end diff --git a/iPhone/CordovaLib/Classes/NSData+Base64.h b/iPhone/CordovaLib/Classes/NSData+Base64.h new file mode 100755 index 0000000..ffe9c83 --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSData+Base64.h @@ -0,0 +1,33 @@ +// +// NSData+Base64.h +// base64 +// +// Created by Matt Gallagher on 2009/06/03. +// Copyright 2009 Matt Gallagher. All rights reserved. +// +// Permission is given to use this source code file, free of charge, in any +// project, commercial or otherwise, entirely at your risk, with the condition +// that any redistribution (in part or whole) of source code must retain +// this copyright and permission notice. Attribution in compiled projects is +// appreciated but not required. +// + +#import <Foundation/Foundation.h> + +void *CDVNewBase64Decode( + const char* inputBuffer, + size_t length, + size_t * outputLength); + +char *CDVNewBase64Encode( + const void* inputBuffer, + size_t length, + bool separateLines, + size_t * outputLength); + +@interface NSData (CDVBase64) + ++ (NSData*)dataFromBase64String:(NSString*)aString; +- (NSString*)base64EncodedString; + +@end diff --git a/iPhone/CordovaLib/Classes/NSData+Base64.m b/iPhone/CordovaLib/Classes/NSData+Base64.m new file mode 100755 index 0000000..08c801b --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSData+Base64.m @@ -0,0 +1,286 @@ +// +// NSData+Base64.m +// base64 +// +// Created by Matt Gallagher on 2009/06/03. +// Copyright 2009 Matt Gallagher. All rights reserved. +// +// Permission is given to use this source code file, free of charge, in any +// project, commercial or otherwise, entirely at your risk, with the condition +// that any redistribution (in part or whole) of source code must retain +// this copyright and permission notice. Attribution in compiled projects is +// appreciated but not required. +// + +#import "NSData+Base64.h" + +// +// Mapping from 6 bit pattern to ASCII character. +// +static unsigned char cdvbase64EncodeLookup[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// +// Definition for "masked-out" areas of the base64DecodeLookup mapping +// +#define xx 65 + +// +// Mapping from ASCII character to 6 bit pattern. +// +static unsigned char cdvbase64DecodeLookup[256] = +{ + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, + xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, + xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, +}; + +// +// Fundamental sizes of the binary and base64 encode/decode units in bytes +// +#define CDV_BINARY_UNIT_SIZE 3 +#define CDV_BASE64_UNIT_SIZE 4 + +// +// NewBase64Decode +// +// Decodes the base64 ASCII string in the inputBuffer to a newly malloced +// output buffer. +// +// inputBuffer - the source ASCII string for the decode +// length - the length of the string or -1 (to specify strlen should be used) +// outputLength - if not-NULL, on output will contain the decoded length +// +// returns the decoded buffer. Must be freed by caller. Length is given by +// outputLength. +// +void *CDVNewBase64Decode( + const char* inputBuffer, + size_t length, + size_t * outputLength) +{ + if (length == -1) { + length = strlen(inputBuffer); + } + + size_t outputBufferSize = (length / CDV_BASE64_UNIT_SIZE) * CDV_BINARY_UNIT_SIZE; + unsigned char* outputBuffer = (unsigned char*)malloc(outputBufferSize); + + size_t i = 0; + size_t j = 0; + + while (i < length) { + // + // Accumulate 4 valid characters (ignore everything else) + // + unsigned char accumulated[CDV_BASE64_UNIT_SIZE]; + bzero(accumulated, sizeof(unsigned char) * CDV_BASE64_UNIT_SIZE); + size_t accumulateIndex = 0; + + while (i < length) { + unsigned char decode = cdvbase64DecodeLookup[inputBuffer[i++]]; + if (decode != xx) { + accumulated[accumulateIndex] = decode; + accumulateIndex++; + + if (accumulateIndex == CDV_BASE64_UNIT_SIZE) { + break; + } + } + } + + // + // Store the 6 bits from each of the 4 characters as 3 bytes + // + outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4); + outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2); + outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3]; + j += accumulateIndex - 1; + } + + if (outputLength) { + *outputLength = j; + } + return outputBuffer; +} + +// +// NewBase64Decode +// +// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced +// output buffer. +// +// inputBuffer - the source data for the encode +// length - the length of the input in bytes +// separateLines - if zero, no CR/LF characters will be added. Otherwise +// a CR/LF pair will be added every 64 encoded chars. +// outputLength - if not-NULL, on output will contain the encoded length +// (not including terminating 0 char) +// +// returns the encoded buffer. Must be freed by caller. Length is given by +// outputLength. +// +char *CDVNewBase64Encode( + const void* buffer, + size_t length, + bool separateLines, + size_t * outputLength) +{ + const unsigned char* inputBuffer = (const unsigned char*)buffer; + +#define MAX_NUM_PADDING_CHARS 2 +#define OUTPUT_LINE_LENGTH 64 +#define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / CDV_BASE64_UNIT_SIZE) * CDV_BINARY_UNIT_SIZE) +#define CR_LF_SIZE 0 + + // + // Byte accurate calculation of final buffer size + // + size_t outputBufferSize = + ((length / CDV_BINARY_UNIT_SIZE) + + ((length % CDV_BINARY_UNIT_SIZE) ? 1 : 0)) + * CDV_BASE64_UNIT_SIZE; + if (separateLines) { + outputBufferSize += + (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE; + } + + // + // Include space for a terminating zero + // + outputBufferSize += 1; + + // + // Allocate the output buffer + // + char* outputBuffer = (char*)malloc(outputBufferSize); + if (!outputBuffer) { + return NULL; + } + + size_t i = 0; + size_t j = 0; + const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length; + size_t lineEnd = lineLength; + + while (true) { + if (lineEnd > length) { + lineEnd = length; + } + + for (; i + CDV_BINARY_UNIT_SIZE - 1 < lineEnd; i += CDV_BINARY_UNIT_SIZE) { + // + // Inner loop: turn 48 bytes into 64 base64 characters + // + outputBuffer[j++] = cdvbase64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = cdvbase64EncodeLookup[((inputBuffer[i] & 0x03) << 4) + | ((inputBuffer[i + 1] & 0xF0) >> 4)]; + outputBuffer[j++] = cdvbase64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2) + | ((inputBuffer[i + 2] & 0xC0) >> 6)]; + outputBuffer[j++] = cdvbase64EncodeLookup[inputBuffer[i + 2] & 0x3F]; + } + + if (lineEnd == length) { + break; + } + + // + // Add the newline + // + // outputBuffer[j++] = '\r'; + // outputBuffer[j++] = '\n'; + lineEnd += lineLength; + } + + if (i + 1 < length) { + // + // Handle the single '=' case + // + outputBuffer[j++] = cdvbase64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = cdvbase64EncodeLookup[((inputBuffer[i] & 0x03) << 4) + | ((inputBuffer[i + 1] & 0xF0) >> 4)]; + outputBuffer[j++] = cdvbase64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2]; + outputBuffer[j++] = '='; + } else if (i < length) { + // + // Handle the double '=' case + // + outputBuffer[j++] = cdvbase64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = cdvbase64EncodeLookup[(inputBuffer[i] & 0x03) << 4]; + outputBuffer[j++] = '='; + outputBuffer[j++] = '='; + } + outputBuffer[j] = 0; + + // + // Set the output length and return the buffer + // + if (outputLength) { + *outputLength = j; + } + return outputBuffer; +} + +@implementation NSData (CDVBase64) + +// +// dataFromBase64String: +// +// Creates an NSData object containing the base64 decoded representation of +// the base64 string 'aString' +// +// Parameters: +// aString - the base64 string to decode +// +// returns the autoreleased NSData representation of the base64 string +// ++ (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]; + + free(outputBuffer); + return result; +} + +// +// base64EncodedString +// +// Creates an NSString object that contains the base 64 encoding of the +// receiver's data. Lines are broken at 64 characters long. +// +// returns an autoreleased NSString being the base 64 representation of the +// receiver. +// +- (NSString*)base64EncodedString +{ + size_t outputLength = 0; + char* outputBuffer = + CDVNewBase64Encode([self bytes], [self length], true, &outputLength); + + NSString* result = + [[NSString alloc] + initWithBytes:outputBuffer + length:outputLength + encoding:NSASCIIStringEncoding]; + + free(outputBuffer); + return result; +} + +@end diff --git a/iPhone/CordovaLib/Classes/NSDictionary+Extensions.h b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.h new file mode 100755 index 0000000..d38a6fc --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.h @@ -0,0 +1,35 @@ +/* + 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 NSDictionary (org_apache_cordova_NSDictionary_Extension) + +- (bool)existsValue:(NSString*)expectedValue forKey:(NSString*)key; +- (NSInteger)integerValueForKey:(NSString*)key defaultValue:(NSInteger)defaultValue withRange:(NSRange)range; +- (NSInteger)integerValueForKey:(NSString*)key defaultValue:(NSInteger)defaultValue; +- (BOOL)typeValueForKey:(NSString*)key isArray:(BOOL*)bArray isNull:(BOOL*)bNull isNumber:(BOOL*)bNumber isString:(BOOL*)bString; +- (BOOL)valueForKeyIsArray:(NSString*)key; +- (BOOL)valueForKeyIsNull:(NSString*)key; +- (BOOL)valueForKeyIsString:(NSString*)key; +- (BOOL)valueForKeyIsNumber:(NSString*)key; + +- (NSDictionary*)dictionaryWithLowercaseKeys; + +@end diff --git a/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m new file mode 100755 index 0000000..80e9ac1 --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m @@ -0,0 +1,159 @@ +/* + 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 "NSDictionary+Extensions.h" +#import <math.h> + +@implementation NSDictionary (org_apache_cordova_NSDictionary_Extension) + +- (bool)existsValue:(NSString*)expectedValue forKey:(NSString*)key +{ + id val = [self valueForKey:key]; + bool exists = false; + + if (val != nil) { + exists = [(NSString*) val compare:expectedValue options:NSCaseInsensitiveSearch] == 0; + } + + return exists; +} + +- (NSInteger)integerValueForKey:(NSString*)key defaultValue:(NSInteger)defaultValue withRange:(NSRange)range +{ + NSInteger value = defaultValue; + + NSNumber* val = [self valueForKey:key]; // value is an NSNumber + + if (val != nil) { + value = [val integerValue]; + } + + // min, max checks + value = MAX(range.location, value); + value = MIN(range.length, value); + + return value; +} + +- (NSInteger)integerValueForKey:(NSString*)key defaultValue:(NSInteger)defaultValue +{ + NSInteger value = defaultValue; + + NSNumber* val = [self valueForKey:key]; // value is an NSNumber + + if (val != nil) { + value = [val integerValue]; + } + return value; +} + +/* + * Determine the type of object stored in a dictionary + * IN: + * (BOOL*) bString - if exists will be set to YES if object is an NSString, NO if not + * (BOOL*) bNull - if exists will be set to YES if object is an NSNull, NO if not + * (BOOL*) bArray - if exists will be set to YES if object is an NSArray, NO if not + * (BOOL*) bNumber - if exists will be set to YES if object is an NSNumber, NO if not + * + * OUT: + * YES if key exists + * NO if key does not exist. Input parameters remain untouched + * + */ + +- (BOOL)typeValueForKey:(NSString*)key isArray:(BOOL*)bArray isNull:(BOOL*)bNull isNumber:(BOOL*)bNumber isString:(BOOL*)bString +{ + BOOL bExists = YES; + NSObject* value = [self objectForKey:key]; + + if (value) { + bExists = YES; + if (bString) { + *bString = [value isKindOfClass:[NSString class]]; + } + if (bNull) { + *bNull = [value isKindOfClass:[NSNull class]]; + } + if (bArray) { + *bArray = [value isKindOfClass:[NSArray class]]; + } + if (bNumber) { + *bNumber = [value isKindOfClass:[NSNumber class]]; + } + } + return bExists; +} + +- (BOOL)valueForKeyIsArray:(NSString*)key +{ + BOOL bArray = NO; + NSObject* value = [self objectForKey:key]; + + if (value) { + bArray = [value isKindOfClass:[NSArray class]]; + } + return bArray; +} + +- (BOOL)valueForKeyIsNull:(NSString*)key +{ + BOOL bNull = NO; + NSObject* value = [self objectForKey:key]; + + if (value) { + bNull = [value isKindOfClass:[NSNull class]]; + } + return bNull; +} + +- (BOOL)valueForKeyIsString:(NSString*)key +{ + BOOL bString = NO; + NSObject* value = [self objectForKey:key]; + + if (value) { + bString = [value isKindOfClass:[NSString class]]; + } + return bString; +} + +- (BOOL)valueForKeyIsNumber:(NSString*)key +{ + BOOL bNumber = NO; + NSObject* value = [self objectForKey:key]; + + if (value) { + bNumber = [value isKindOfClass:[NSNumber class]]; + } + return bNumber; +} + +- (NSDictionary*)dictionaryWithLowercaseKeys +{ + NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:self.count]; + NSString* key; + + for (key in self) { + [result setObject:[self objectForKey:key] forKey:[key lowercaseString]]; + } + + return result; +} + +@end diff --git a/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.h b/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.h new file mode 100755 index 0000000..3194094 --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.h @@ -0,0 +1,29 @@ +/* + 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 NSMutableArray (QueueAdditions) + +- (id)pop; +- (id)queueHead; +- (id)dequeue; +- (void)enqueue:(id)obj; + +@end diff --git a/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.m b/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.m new file mode 100755 index 0000000..9e67ede --- /dev/null +++ b/iPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.m @@ -0,0 +1,58 @@ +/* + 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 "NSMutableArray+QueueAdditions.h" + +@implementation NSMutableArray (QueueAdditions) + +- (id)queueHead +{ + if ([self count] == 0) { + return nil; + } + + return [self objectAtIndex:0]; +} + +- (__autoreleasing id)dequeue +{ + if ([self count] == 0) { + return nil; + } + + id head = [self objectAtIndex:0]; + if (head != nil) { + // [[head retain] autorelease]; ARC - the __autoreleasing on the return value should so the same thing + [self removeObjectAtIndex:0]; + } + + return head; +} + +- (id)pop +{ + return [self dequeue]; +} + +- (void)enqueue:(id)object +{ + [self addObject:object]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/UIDevice+Extensions.h b/iPhone/CordovaLib/Classes/UIDevice+Extensions.h new file mode 100755 index 0000000..dab73e6 --- /dev/null +++ b/iPhone/CordovaLib/Classes/UIDevice+Extensions.h @@ -0,0 +1,31 @@ +/* + 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 UIDevice (org_apache_cordova_UIDevice_Extension) + +/* + Get the unique identifier from the app bundle's folder, which is already a GUID + Upgrading and/or deleting the app and re-installing will get you a new GUID, so + this is only unique per install per device. + */ +- (NSString*)uniqueAppInstanceIdentifier; + +@end diff --git a/iPhone/CordovaLib/Classes/UIDevice+Extensions.m b/iPhone/CordovaLib/Classes/UIDevice+Extensions.m new file mode 100755 index 0000000..c0c91af --- /dev/null +++ b/iPhone/CordovaLib/Classes/UIDevice+Extensions.m @@ -0,0 +1,47 @@ +/* + 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 <UIKit/UIKit.h> +#import "UIDevice+Extensions.h" + +@implementation UIDevice (org_apache_cordova_UIDevice_Extension) + +- (NSString*)uniqueAppInstanceIdentifier +{ + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + static NSString* UUID_KEY = @"CDVUUID"; + + NSString* app_uuid = [userDefaults stringForKey:UUID_KEY]; + + if (app_uuid == nil) { + CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); + CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); + + app_uuid = [NSString stringWithString:(__bridge NSString*)uuidString]; + [userDefaults setObject:app_uuid forKey:UUID_KEY]; + [userDefaults synchronize]; + + CFRelease(uuidString); + CFRelease(uuidRef); + } + + return app_uuid; +} + +@end diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h new file mode 100755 index 0000000..9794fa2 --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h @@ -0,0 +1,30 @@ +/* + 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 new file mode 100755 index 0000000..7a06e51 --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h @@ -0,0 +1,46 @@ +/* + 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 new file mode 100755 index 0000000..52ccd41 --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m @@ -0,0 +1,29 @@ +/* + 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 new file mode 100755 index 0000000..1c76eaa --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h @@ -0,0 +1,32 @@ +/* + 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 new file mode 100755 index 0000000..6e2c4c3 --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h @@ -0,0 +1,23 @@ +/* + 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 new file mode 100755 index 0000000..8c2d644 --- /dev/null +++ b/iPhone/CordovaLib/Classes/compatibility/README.txt @@ -0,0 +1,23 @@ +# +# 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 diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj new file mode 100755 index 0000000..1bb7fa7 --- /dev/null +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -0,0 +1,644 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1F2BECC013F9785B00A93BF6 /* CDVBattery.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F2BECBE13F9785B00A93BF6 /* CDVBattery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1F2BECC113F9785B00A93BF6 /* CDVBattery.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F2BECBF13F9785B00A93BF6 /* CDVBattery.m */; }; + 1F3C04CE12BC247D004F9E10 /* CDVContact.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F3C04CC12BC247D004F9E10 /* CDVContact.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1F3C04CF12BC247D004F9E10 /* CDVContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F3C04CD12BC247D004F9E10 /* CDVContact.m */; }; + 1F584B9B1385A28A00ED25E8 /* CDVCapture.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F584B991385A28900ED25E8 /* CDVCapture.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1F584B9C1385A28A00ED25E8 /* CDVCapture.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F584B9A1385A28900ED25E8 /* CDVCapture.m */; }; + 1F92F4A01314023E0046367C /* CDVPluginResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F92F49E1314023E0046367C /* CDVPluginResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1F92F4A11314023E0046367C /* CDVPluginResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F92F49F1314023E0046367C /* CDVPluginResult.m */; }; + 301F2F2A14F3C9CA003FE9FC /* CDV.h in Headers */ = {isa = PBXBuildFile; fileRef = 301F2F2914F3C9CA003FE9FC /* CDV.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 302965BC13A94E9D007046C5 /* CDVDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 302965BB13A94E9D007046C5 /* CDVDebug.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3034979C1513D56A0090E688 /* CDVLocalStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 3034979A1513D56A0090E688 /* CDVLocalStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3034979E1513D56A0090E688 /* CDVLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 3034979B1513D56A0090E688 /* CDVLocalStorage.m */; }; + 30392E4E14F4FCAB00B9E0B8 /* CDVAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 30392E4D14F4FCAB00B9E0B8 /* CDVAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3062D120151D0EDB000D9128 /* UIDevice+Extensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3062D11E151D0EDB000D9128 /* UIDevice+Extensions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3062D122151D0EDB000D9128 /* UIDevice+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3062D11F151D0EDB000D9128 /* UIDevice+Extensions.m */; }; + 3073E9E91656D37700957977 /* CDVInAppBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 3073E9E71656D37700957977 /* CDVInAppBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3073E9EA1656D37700957977 /* CDVInAppBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 3073E9E81656D37700957977 /* CDVInAppBrowser.m */; }; + 3073E9ED1656D51200957977 /* CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 307A8F9E1385A2EC00E43782 /* CDVConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 307A8F9C1385A2EC00E43782 /* CDVConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 307A8F9F1385A2EC00E43782 /* CDVConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 307A8F9D1385A2EC00E43782 /* CDVConnection.m */; }; + 30A90B9114588697006178D3 /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 30A90B8F14588697006178D3 /* JSONKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30A90B9314588697006178D3 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 30A90B9014588697006178D3 /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 30B39EBE13D0268B0009682A /* CDVSplashScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 30B39EBC13D0268B0009682A /* CDVSplashScreen.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30B39EBF13D0268B0009682A /* CDVSplashScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 30B39EBD13D0268B0009682A /* CDVSplashScreen.m */; }; + 30C5F1DF15AF9E950052A00D /* CDVDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 30C5F1DD15AF9E950052A00D /* CDVDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30C5F1E015AF9E950052A00D /* CDVDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C5F1DE15AF9E950052A00D /* CDVDevice.m */; }; + 30C684801406CB38004C1A8E /* CDVWhitelist.h in Headers */ = {isa = PBXBuildFile; fileRef = 30C6847E1406CB38004C1A8E /* CDVWhitelist.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30C684821406CB38004C1A8E /* CDVWhitelist.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C6847F1406CB38004C1A8E /* CDVWhitelist.m */; }; + 30C684941407044B004C1A8E /* CDVURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 30C684921407044A004C1A8E /* CDVURLProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30C684961407044B004C1A8E /* CDVURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C684931407044A004C1A8E /* CDVURLProtocol.m */; }; + 30E33AF213A7E24B00594D64 /* CDVPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E33AF013A7E24B00594D64 /* CDVPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30E33AF313A7E24B00594D64 /* CDVPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 30E33AF113A7E24B00594D64 /* CDVPlugin.m */; }; + 30E563CF13E217EC00C949AA /* NSMutableArray+QueueAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E563CD13E217EC00C949AA /* NSMutableArray+QueueAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 30E563D013E217EC00C949AA /* NSMutableArray+QueueAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */; }; + 30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */; }; + 3E76876F156A90EE00EB6FA3 /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43614B65FD800F0E735 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43714B65FD800F0E735 /* CDVViewController.m */; }; + 8852C43F14B65FD800F0E735 /* CDVCordovaView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43814B65FD800F0E735 /* CDVCordovaView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8852C44114B65FD800F0E735 /* CDVCordovaView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43914B65FD800F0E735 /* CDVCordovaView.m */; }; + 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD261090FBE7009987E8 /* CDVCamera.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD671090FBE7009987E8 /* CDVCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD271090FBE7009987E8 /* CDVCamera.m */; }; + 8887FD681090FBE7009987E8 /* NSDictionary+Extensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD281090FBE7009987E8 /* NSDictionary+Extensions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */; }; + 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2A1090FBE7009987E8 /* CDVContacts.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2B1090FBE7009987E8 /* CDVContacts.m */; }; + 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */; }; + 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD301090FBE7009987E8 /* CDVFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD311090FBE7009987E8 /* CDVFile.m */; }; + 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD751090FBE7009987E8 /* CDVInvokedUrlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD351090FBE7009987E8 /* CDVInvokedUrlCommand.m */; }; + 8887FD851090FBE7009987E8 /* CDVLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD461090FBE7009987E8 /* CDVLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD861090FBE7009987E8 /* CDVLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD471090FBE7009987E8 /* CDVLocation.m */; }; + 8887FD8D1090FBE7009987E8 /* CDVNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD4E1090FBE7009987E8 /* CDVNotification.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD8E1090FBE7009987E8 /* CDVNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD4F1090FBE7009987E8 /* CDVNotification.m */; }; + 8887FD8F1090FBE7009987E8 /* NSData+Base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD501090FBE7009987E8 /* NSData+Base64.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD901090FBE7009987E8 /* NSData+Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD511090FBE7009987E8 /* NSData+Base64.m */; }; + 8887FD9D1090FBE7009987E8 /* CDVReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD5E1090FBE7009987E8 /* CDVReachability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FD9E1090FBE7009987E8 /* CDVReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD5F1090FBE7009987E8 /* CDVReachability.m */; }; + 8887FD9F1090FBE7009987E8 /* CDVSound.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD601090FBE7009987E8 /* CDVSound.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8887FDA01090FBE7009987E8 /* CDVSound.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD611090FBE7009987E8 /* CDVSound.m */; }; + 88BA573D109BB46F00FB5E78 /* CDVAccelerometer.h in Headers */ = {isa = PBXBuildFile; fileRef = 88BA573B109BB46F00FB5E78 /* CDVAccelerometer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88BA573E109BB46F00FB5E78 /* CDVAccelerometer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88BA573C109BB46F00FB5E78 /* CDVAccelerometer.m */; }; + 9D76CF3C1625A4C50008A0F6 /* CDVGlobalization.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D76CF3A1625A4C50008A0F6 /* CDVGlobalization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D76CF3D1625A4C50008A0F6 /* CDVGlobalization.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D76CF3B1625A4C50008A0F6 /* CDVGlobalization.m */; }; + C937A4561337599E002C4C79 /* CDVFileTransfer.h in Headers */ = {isa = PBXBuildFile; fileRef = C937A4541337599E002C4C79 /* CDVFileTransfer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C937A4571337599E002C4C79 /* CDVFileTransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = C937A4551337599E002C4C79 /* CDVFileTransfer.m */; }; + EB3B3547161CB44D003DBE7D /* CDVCommandQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = EB3B3545161CB44D003DBE7D /* CDVCommandQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EB3B3548161CB44D003DBE7D /* CDVCommandQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = EB3B3546161CB44D003DBE7D /* CDVCommandQueue.m */; }; + EB3B357C161F2A45003DBE7D /* CDVCommandDelegateImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = EB3B357A161F2A44003DBE7D /* CDVCommandDelegateImpl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EB3B357D161F2A45003DBE7D /* CDVCommandDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = EB3B357B161F2A45003DBE7D /* CDVCommandDelegateImpl.m */; }; + EB80C2AC15DEA63D004D9E7B /* CDVEcho.h in Headers */ = {isa = PBXBuildFile; fileRef = EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EB80C2AD15DEA63D004D9E7B /* CDVEcho.m in Sources */ = {isa = PBXBuildFile; fileRef = EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */; }; + EBA3557315ABD38C00F4DE24 /* NSArray+Comparisons.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EBA3557515ABD38C00F4DE24 /* NSArray+Comparisons.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */; }; + F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = F858FBC4166009A8007DA594 /* CDVConfigParser.h */; }; + F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F858FBC5166009A8007DA594 /* CDVConfigParser.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1F2BECBE13F9785B00A93BF6 /* CDVBattery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVBattery.h; path = Classes/CDVBattery.h; sourceTree = "<group>"; }; + 1F2BECBF13F9785B00A93BF6 /* CDVBattery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVBattery.m; path = Classes/CDVBattery.m; sourceTree = "<group>"; }; + 1F3C04CC12BC247D004F9E10 /* CDVContact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVContact.h; path = Classes/CDVContact.h; sourceTree = "<group>"; }; + 1F3C04CD12BC247D004F9E10 /* CDVContact.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVContact.m; path = Classes/CDVContact.m; sourceTree = "<group>"; }; + 1F584B991385A28900ED25E8 /* CDVCapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCapture.h; path = Classes/CDVCapture.h; sourceTree = "<group>"; }; + 1F584B9A1385A28900ED25E8 /* CDVCapture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCapture.m; path = Classes/CDVCapture.m; sourceTree = "<group>"; }; + 1F92F49E1314023E0046367C /* CDVPluginResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVPluginResult.h; path = Classes/CDVPluginResult.h; sourceTree = "<group>"; }; + 1F92F49F1314023E0046367C /* CDVPluginResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVPluginResult.m; path = Classes/CDVPluginResult.m; sourceTree = "<group>"; }; + 301F2F2914F3C9CA003FE9FC /* CDV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDV.h; path = Classes/CDV.h; sourceTree = "<group>"; }; + 302965BB13A94E9D007046C5 /* CDVDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDebug.h; path = Classes/CDVDebug.h; sourceTree = "<group>"; }; + 30325A0B136B343700982B63 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; }; + 3034979A1513D56A0090E688 /* CDVLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVLocalStorage.h; path = Classes/CDVLocalStorage.h; sourceTree = "<group>"; }; + 3034979B1513D56A0090E688 /* CDVLocalStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVLocalStorage.m; path = Classes/CDVLocalStorage.m; sourceTree = "<group>"; }; + 30392E4D14F4FCAB00B9E0B8 /* CDVAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVAvailability.h; path = Classes/CDVAvailability.h; sourceTree = "<group>"; }; + 3062D11E151D0EDB000D9128 /* UIDevice+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIDevice+Extensions.h"; path = "Classes/UIDevice+Extensions.h"; sourceTree = "<group>"; }; + 3062D11F151D0EDB000D9128 /* UIDevice+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIDevice+Extensions.m"; path = "Classes/UIDevice+Extensions.m"; sourceTree = "<group>"; }; + 3073E9E71656D37700957977 /* CDVInAppBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVInAppBrowser.h; path = Classes/CDVInAppBrowser.h; sourceTree = "<group>"; }; + 3073E9E81656D37700957977 /* CDVInAppBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVInAppBrowser.m; path = Classes/CDVInAppBrowser.m; sourceTree = "<group>"; }; + 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVScreenOrientationDelegate.h; path = Classes/CDVScreenOrientationDelegate.h; sourceTree = "<group>"; }; + 307A8F9C1385A2EC00E43782 /* CDVConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVConnection.h; path = Classes/CDVConnection.h; sourceTree = "<group>"; }; + 307A8F9D1385A2EC00E43782 /* CDVConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVConnection.m; path = Classes/CDVConnection.m; sourceTree = "<group>"; }; + 30A90B8F14588697006178D3 /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; }; + 30A90B9014588697006178D3 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; }; + 30B39EBC13D0268B0009682A /* CDVSplashScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVSplashScreen.h; path = Classes/CDVSplashScreen.h; sourceTree = "<group>"; }; + 30B39EBD13D0268B0009682A /* CDVSplashScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVSplashScreen.m; path = Classes/CDVSplashScreen.m; sourceTree = "<group>"; }; + 30C5F1DD15AF9E950052A00D /* CDVDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDevice.h; path = Classes/CDVDevice.h; sourceTree = "<group>"; }; + 30C5F1DE15AF9E950052A00D /* CDVDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVDevice.m; path = Classes/CDVDevice.m; sourceTree = "<group>"; }; + 30C6847E1406CB38004C1A8E /* CDVWhitelist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVWhitelist.h; path = Classes/CDVWhitelist.h; sourceTree = "<group>"; }; + 30C6847F1406CB38004C1A8E /* CDVWhitelist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVWhitelist.m; path = Classes/CDVWhitelist.m; sourceTree = "<group>"; }; + 30C684921407044A004C1A8E /* CDVURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVURLProtocol.h; path = Classes/CDVURLProtocol.h; sourceTree = "<group>"; }; + 30C684931407044A004C1A8E /* CDVURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVURLProtocol.m; path = Classes/CDVURLProtocol.m; sourceTree = "<group>"; }; + 30E33AF013A7E24B00594D64 /* CDVPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVPlugin.h; path = Classes/CDVPlugin.h; sourceTree = "<group>"; }; + 30E33AF113A7E24B00594D64 /* CDVPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVPlugin.m; path = Classes/CDVPlugin.m; sourceTree = "<group>"; }; + 30E563CD13E217EC00C949AA /* NSMutableArray+QueueAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableArray+QueueAdditions.h"; path = "Classes/NSMutableArray+QueueAdditions.h"; sourceTree = "<group>"; }; + 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableArray+QueueAdditions.m"; path = "Classes/NSMutableArray+QueueAdditions.m"; sourceTree = "<group>"; }; + 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCommandDelegate.h; path = Classes/CDVCommandDelegate.h; sourceTree = "<group>"; }; + 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVLogger.m; path = Classes/CDVLogger.m; sourceTree = "<group>"; }; + 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVLogger.h; path = Classes/CDVLogger.h; sourceTree = "<group>"; }; + 686357AA141002F100DF4CF2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 686357AC141002F100DF4CF2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 686357AE141002F100DF4CF2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 686357CC14100AAD00DF4CF2 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; + 686357CE14100ADA00DF4CF2 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 686357CF14100ADB00DF4CF2 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 686357D014100ADE00DF4CF2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + 686357D214100AE700DF4CF2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 686357D414100AF200DF4CF2 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 686357DC14100B1600DF4CF2 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + 68A32D7114102E1C006B237C /* libCordova.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCordova.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 68A32D7414103017006B237C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + 8852C43614B65FD800F0E735 /* CDVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVViewController.h; path = Classes/CDVViewController.h; sourceTree = "<group>"; }; + 8852C43714B65FD800F0E735 /* CDVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVViewController.m; path = Classes/CDVViewController.m; sourceTree = "<group>"; }; + 8852C43814B65FD800F0E735 /* CDVCordovaView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCordovaView.h; path = Classes/CDVCordovaView.h; sourceTree = "<group>"; }; + 8852C43914B65FD800F0E735 /* CDVCordovaView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCordovaView.m; path = Classes/CDVCordovaView.m; sourceTree = "<group>"; }; + 8887FD261090FBE7009987E8 /* CDVCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCamera.h; path = Classes/CDVCamera.h; sourceTree = "<group>"; }; + 8887FD271090FBE7009987E8 /* CDVCamera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCamera.m; path = Classes/CDVCamera.m; sourceTree = "<group>"; }; + 8887FD281090FBE7009987E8 /* NSDictionary+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Extensions.h"; path = "Classes/NSDictionary+Extensions.h"; sourceTree = "<group>"; }; + 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Extensions.m"; path = "Classes/NSDictionary+Extensions.m"; sourceTree = "<group>"; }; + 8887FD2A1090FBE7009987E8 /* CDVContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVContacts.h; path = Classes/CDVContacts.h; sourceTree = "<group>"; }; + 8887FD2B1090FBE7009987E8 /* CDVContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVContacts.m; path = Classes/CDVContacts.m; sourceTree = "<group>"; }; + 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDebugConsole.h; path = Classes/CDVDebugConsole.h; sourceTree = "<group>"; }; + 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVDebugConsole.m; path = Classes/CDVDebugConsole.m; sourceTree = "<group>"; }; + 8887FD301090FBE7009987E8 /* CDVFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVFile.h; path = Classes/CDVFile.h; sourceTree = "<group>"; }; + 8887FD311090FBE7009987E8 /* CDVFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVFile.m; path = Classes/CDVFile.m; sourceTree = "<group>"; }; + 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVInvokedUrlCommand.h; path = Classes/CDVInvokedUrlCommand.h; sourceTree = "<group>"; }; + 8887FD351090FBE7009987E8 /* CDVInvokedUrlCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVInvokedUrlCommand.m; path = Classes/CDVInvokedUrlCommand.m; sourceTree = "<group>"; }; + 8887FD461090FBE7009987E8 /* CDVLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVLocation.h; path = Classes/CDVLocation.h; sourceTree = "<group>"; }; + 8887FD471090FBE7009987E8 /* CDVLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVLocation.m; path = Classes/CDVLocation.m; sourceTree = "<group>"; }; + 8887FD4E1090FBE7009987E8 /* CDVNotification.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVNotification.h; path = Classes/CDVNotification.h; sourceTree = "<group>"; }; + 8887FD4F1090FBE7009987E8 /* CDVNotification.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVNotification.m; path = Classes/CDVNotification.m; sourceTree = "<group>"; }; + 8887FD501090FBE7009987E8 /* NSData+Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+Base64.h"; path = "Classes/NSData+Base64.h"; sourceTree = "<group>"; }; + 8887FD511090FBE7009987E8 /* NSData+Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+Base64.m"; path = "Classes/NSData+Base64.m"; sourceTree = "<group>"; }; + 8887FD5E1090FBE7009987E8 /* CDVReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVReachability.h; path = Classes/CDVReachability.h; sourceTree = "<group>"; }; + 8887FD5F1090FBE7009987E8 /* CDVReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVReachability.m; path = Classes/CDVReachability.m; sourceTree = "<group>"; }; + 8887FD601090FBE7009987E8 /* CDVSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVSound.h; path = Classes/CDVSound.h; sourceTree = "<group>"; }; + 8887FD611090FBE7009987E8 /* CDVSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVSound.m; path = Classes/CDVSound.m; sourceTree = "<group>"; }; + 88BA573B109BB46F00FB5E78 /* CDVAccelerometer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVAccelerometer.h; path = Classes/CDVAccelerometer.h; sourceTree = "<group>"; }; + 88BA573C109BB46F00FB5E78 /* CDVAccelerometer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVAccelerometer.m; path = Classes/CDVAccelerometer.m; sourceTree = "<group>"; }; + 9D76CF3A1625A4C50008A0F6 /* CDVGlobalization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVGlobalization.h; path = Classes/CDVGlobalization.h; sourceTree = "<group>"; }; + 9D76CF3B1625A4C50008A0F6 /* CDVGlobalization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVGlobalization.m; path = Classes/CDVGlobalization.m; sourceTree = "<group>"; }; + AA747D9E0F9514B9006C5449 /* CordovaLib_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CordovaLib_Prefix.pch; sourceTree = SOURCE_ROOT; }; + C937A4541337599E002C4C79 /* CDVFileTransfer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVFileTransfer.h; path = Classes/CDVFileTransfer.h; sourceTree = "<group>"; }; + C937A4551337599E002C4C79 /* CDVFileTransfer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVFileTransfer.m; path = Classes/CDVFileTransfer.m; sourceTree = "<group>"; }; + EB3B3545161CB44D003DBE7D /* CDVCommandQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCommandQueue.h; path = Classes/CDVCommandQueue.h; sourceTree = "<group>"; }; + EB3B3546161CB44D003DBE7D /* CDVCommandQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCommandQueue.m; path = Classes/CDVCommandQueue.m; sourceTree = "<group>"; }; + EB3B357A161F2A44003DBE7D /* CDVCommandDelegateImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCommandDelegateImpl.h; path = Classes/CDVCommandDelegateImpl.h; sourceTree = "<group>"; }; + EB3B357B161F2A45003DBE7D /* CDVCommandDelegateImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCommandDelegateImpl.m; path = Classes/CDVCommandDelegateImpl.m; sourceTree = "<group>"; }; + EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVEcho.h; path = Classes/CDVEcho.h; sourceTree = "<group>"; }; + EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVEcho.m; path = Classes/CDVEcho.m; sourceTree = "<group>"; }; + EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSArray+Comparisons.h"; path = "Classes/NSArray+Comparisons.h"; sourceTree = "<group>"; }; + EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+Comparisons.m"; path = "Classes/NSArray+Comparisons.m"; sourceTree = "<group>"; }; + F858FBC4166009A8007DA594 /* CDVConfigParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVConfigParser.h; path = Classes/CDVConfigParser.h; sourceTree = "<group>"; }; + F858FBC5166009A8007DA594 /* CDVConfigParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVConfigParser.m; path = Classes/CDVConfigParser.m; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 68A32D7114102E1C006B237C /* libCordova.a */, + ); + name = Products; + sourceTree = CORDOVALIB; + }; + 0867D691FE84028FC02AAC07 /* CordovaLib */ = { + isa = PBXGroup; + children = ( + 8887FD101090FB43009987E8 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + 30325A0B136B343700982B63 /* VERSION */, + ); + name = CordovaLib; + sourceTree = "<group>"; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 68A32D7414103017006B237C /* AddressBook.framework */, + 686357DC14100B1600DF4CF2 /* CoreMedia.framework */, + 686357CE14100ADA00DF4CF2 /* AudioToolbox.framework */, + 686357CF14100ADB00DF4CF2 /* AVFoundation.framework */, + 686357D014100ADE00DF4CF2 /* CoreLocation.framework */, + 686357D214100AE700DF4CF2 /* MobileCoreServices.framework */, + 686357D414100AF200DF4CF2 /* SystemConfiguration.framework */, + 686357CC14100AAD00DF4CF2 /* AddressBookUI.framework */, + 686357AA141002F100DF4CF2 /* UIKit.framework */, + 686357AC141002F100DF4CF2 /* Foundation.framework */, + 686357AE141002F100DF4CF2 /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 3054098714B77FF3009841CA /* Cleaver */ = { + isa = PBXGroup; + children = ( + F858FBC4166009A8007DA594 /* CDVConfigParser.h */, + F858FBC5166009A8007DA594 /* CDVConfigParser.m */, + 8852C43614B65FD800F0E735 /* CDVViewController.h */, + 8852C43714B65FD800F0E735 /* CDVViewController.m */, + 8852C43814B65FD800F0E735 /* CDVCordovaView.h */, + 8852C43914B65FD800F0E735 /* CDVCordovaView.m */, + EB3B3545161CB44D003DBE7D /* CDVCommandQueue.h */, + EB3B3546161CB44D003DBE7D /* CDVCommandQueue.m */, + ); + name = Cleaver; + sourceTree = "<group>"; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* CordovaLib_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = "<group>"; + }; + 888700D710922F56009987E8 /* Commands */ = { + isa = PBXGroup; + children = ( + 30C5F1DD15AF9E950052A00D /* CDVDevice.h */, + 30C5F1DE15AF9E950052A00D /* CDVDevice.m */, + 301F2F2914F3C9CA003FE9FC /* CDV.h */, + 3034979A1513D56A0090E688 /* CDVLocalStorage.h */, + 3034979B1513D56A0090E688 /* CDVLocalStorage.m */, + 30392E4D14F4FCAB00B9E0B8 /* CDVAvailability.h */, + 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */, + EB3B357A161F2A44003DBE7D /* CDVCommandDelegateImpl.h */, + EB3B357B161F2A45003DBE7D /* CDVCommandDelegateImpl.m */, + 30C684921407044A004C1A8E /* CDVURLProtocol.h */, + 30C684931407044A004C1A8E /* CDVURLProtocol.m */, + 30C6847E1406CB38004C1A8E /* CDVWhitelist.h */, + 30C6847F1406CB38004C1A8E /* CDVWhitelist.m */, + 1F2BECBE13F9785B00A93BF6 /* CDVBattery.h */, + 1F2BECBF13F9785B00A93BF6 /* CDVBattery.m */, + 30B39EBC13D0268B0009682A /* CDVSplashScreen.h */, + 30B39EBD13D0268B0009682A /* CDVSplashScreen.m */, + 30E33AF013A7E24B00594D64 /* CDVPlugin.h */, + 30E33AF113A7E24B00594D64 /* CDVPlugin.m */, + 307A8F9C1385A2EC00E43782 /* CDVConnection.h */, + 307A8F9D1385A2EC00E43782 /* CDVConnection.m */, + 1F92F49E1314023E0046367C /* CDVPluginResult.h */, + 1F92F49F1314023E0046367C /* CDVPluginResult.m */, + 88BA573B109BB46F00FB5E78 /* CDVAccelerometer.h */, + 88BA573C109BB46F00FB5E78 /* CDVAccelerometer.m */, + 8887FD261090FBE7009987E8 /* CDVCamera.h */, + 8887FD271090FBE7009987E8 /* CDVCamera.m */, + 1F584B991385A28900ED25E8 /* CDVCapture.h */, + 1F584B9A1385A28900ED25E8 /* CDVCapture.m */, + 1F3C04CC12BC247D004F9E10 /* CDVContact.h */, + 1F3C04CD12BC247D004F9E10 /* CDVContact.m */, + 8887FD2A1090FBE7009987E8 /* CDVContacts.h */, + 8887FD2B1090FBE7009987E8 /* CDVContacts.m */, + 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */, + 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */, + EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */, + EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */, + 8887FD301090FBE7009987E8 /* CDVFile.h */, + 8887FD311090FBE7009987E8 /* CDVFile.m */, + 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */, + 8887FD351090FBE7009987E8 /* CDVInvokedUrlCommand.m */, + C937A4541337599E002C4C79 /* CDVFileTransfer.h */, + C937A4551337599E002C4C79 /* CDVFileTransfer.m */, + 8887FD461090FBE7009987E8 /* CDVLocation.h */, + 8887FD471090FBE7009987E8 /* CDVLocation.m */, + 8887FD4E1090FBE7009987E8 /* CDVNotification.h */, + 8887FD4F1090FBE7009987E8 /* CDVNotification.m */, + 8887FD5E1090FBE7009987E8 /* CDVReachability.h */, + 8887FD5F1090FBE7009987E8 /* CDVReachability.m */, + 8887FD601090FBE7009987E8 /* CDVSound.h */, + 8887FD611090FBE7009987E8 /* CDVSound.m */, + 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */, + 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */, + 9D76CF3A1625A4C50008A0F6 /* CDVGlobalization.h */, + 9D76CF3B1625A4C50008A0F6 /* CDVGlobalization.m */, + 3073E9E71656D37700957977 /* CDVInAppBrowser.h */, + 3073E9E81656D37700957977 /* CDVInAppBrowser.m */, + 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */, + ); + name = Commands; + sourceTree = "<group>"; + }; + 888700D910923009009987E8 /* Util */ = { + isa = PBXGroup; + children = ( + 3062D11E151D0EDB000D9128 /* UIDevice+Extensions.h */, + 3062D11F151D0EDB000D9128 /* UIDevice+Extensions.m */, + EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */, + EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */, + 8887FD281090FBE7009987E8 /* NSDictionary+Extensions.h */, + 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */, + 302965BB13A94E9D007046C5 /* CDVDebug.h */, + 30E563CD13E217EC00C949AA /* NSMutableArray+QueueAdditions.h */, + 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */, + 8887FD501090FBE7009987E8 /* NSData+Base64.h */, + 8887FD511090FBE7009987E8 /* NSData+Base64.m */, + ); + name = Util; + sourceTree = "<group>"; + }; + 8887FD101090FB43009987E8 /* Classes */ = { + isa = PBXGroup; + children = ( + 3054098714B77FF3009841CA /* Cleaver */, + 888700D710922F56009987E8 /* Commands */, + 8887FD361090FBE7009987E8 /* JSON */, + 888700D910923009009987E8 /* Util */, + ); + name = Classes; + sourceTree = "<group>"; + }; + 8887FD361090FBE7009987E8 /* JSON */ = { + isa = PBXGroup; + children = ( + 30A90B8F14588697006178D3 /* JSONKit.h */, + 30A90B9014588697006178D3 /* JSONKit.m */, + ); + name = JSON; + path = Classes/JSON; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */, + 8887FD681090FBE7009987E8 /* NSDictionary+Extensions.h in Headers */, + 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */, + 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */, + 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */, + 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */, + 8887FD851090FBE7009987E8 /* CDVLocation.h in Headers */, + 8887FD8D1090FBE7009987E8 /* CDVNotification.h in Headers */, + 8887FD8F1090FBE7009987E8 /* NSData+Base64.h in Headers */, + 8887FD9D1090FBE7009987E8 /* CDVReachability.h in Headers */, + 8887FD9F1090FBE7009987E8 /* CDVSound.h in Headers */, + 88BA573D109BB46F00FB5E78 /* CDVAccelerometer.h in Headers */, + 1F3C04CE12BC247D004F9E10 /* CDVContact.h in Headers */, + 1F92F4A01314023E0046367C /* CDVPluginResult.h in Headers */, + C937A4561337599E002C4C79 /* CDVFileTransfer.h in Headers */, + 307A8F9E1385A2EC00E43782 /* CDVConnection.h in Headers */, + 1F584B9B1385A28A00ED25E8 /* CDVCapture.h in Headers */, + 30E33AF213A7E24B00594D64 /* CDVPlugin.h in Headers */, + 302965BC13A94E9D007046C5 /* CDVDebug.h in Headers */, + 30B39EBE13D0268B0009682A /* CDVSplashScreen.h in Headers */, + 30E563CF13E217EC00C949AA /* NSMutableArray+QueueAdditions.h in Headers */, + 1F2BECC013F9785B00A93BF6 /* CDVBattery.h in Headers */, + 30C684801406CB38004C1A8E /* CDVWhitelist.h in Headers */, + 30C684941407044B004C1A8E /* CDVURLProtocol.h in Headers */, + 30A90B9114588697006178D3 /* JSONKit.h in Headers */, + 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */, + 8852C43F14B65FD800F0E735 /* CDVCordovaView.h in Headers */, + 30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */, + 301F2F2A14F3C9CA003FE9FC /* CDV.h in Headers */, + 30392E4E14F4FCAB00B9E0B8 /* CDVAvailability.h in Headers */, + 3034979C1513D56A0090E688 /* CDVLocalStorage.h in Headers */, + 3062D120151D0EDB000D9128 /* UIDevice+Extensions.h in Headers */, + 3E76876F156A90EE00EB6FA3 /* CDVLogger.h in Headers */, + EBA3557315ABD38C00F4DE24 /* NSArray+Comparisons.h in Headers */, + 30C5F1DF15AF9E950052A00D /* CDVDevice.h in Headers */, + EB80C2AC15DEA63D004D9E7B /* CDVEcho.h in Headers */, + EB3B3547161CB44D003DBE7D /* CDVCommandQueue.h in Headers */, + EB3B357C161F2A45003DBE7D /* CDVCommandDelegateImpl.h in Headers */, + 9D76CF3C1625A4C50008A0F6 /* CDVGlobalization.h in Headers */, + 3073E9E91656D37700957977 /* CDVInAppBrowser.h in Headers */, + 3073E9ED1656D51200957977 /* CDVScreenOrientationDelegate.h in Headers */, + F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC07D0554694100DB518D /* CordovaLib */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CordovaLib" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CordovaLib; + productName = CordovaLib; + productReference = 68A32D7114102E1C006B237C /* libCordova.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0430; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CordovaLib" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* CordovaLib */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* CordovaLib */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8887FD671090FBE7009987E8 /* CDVCamera.m in Sources */, + 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */, + 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */, + 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */, + 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */, + 8887FD751090FBE7009987E8 /* CDVInvokedUrlCommand.m in Sources */, + 8887FD861090FBE7009987E8 /* CDVLocation.m in Sources */, + 8887FD8E1090FBE7009987E8 /* CDVNotification.m in Sources */, + 8887FD901090FBE7009987E8 /* NSData+Base64.m in Sources */, + 8887FD9E1090FBE7009987E8 /* CDVReachability.m in Sources */, + 8887FDA01090FBE7009987E8 /* CDVSound.m in Sources */, + 88BA573E109BB46F00FB5E78 /* CDVAccelerometer.m in Sources */, + 1F3C04CF12BC247D004F9E10 /* CDVContact.m in Sources */, + 1F92F4A11314023E0046367C /* CDVPluginResult.m in Sources */, + C937A4571337599E002C4C79 /* CDVFileTransfer.m in Sources */, + 307A8F9F1385A2EC00E43782 /* CDVConnection.m in Sources */, + 1F584B9C1385A28A00ED25E8 /* CDVCapture.m in Sources */, + 30E33AF313A7E24B00594D64 /* CDVPlugin.m in Sources */, + 30B39EBF13D0268B0009682A /* CDVSplashScreen.m in Sources */, + 30E563D013E217EC00C949AA /* NSMutableArray+QueueAdditions.m in Sources */, + 1F2BECC113F9785B00A93BF6 /* CDVBattery.m in Sources */, + 30C684821406CB38004C1A8E /* CDVWhitelist.m in Sources */, + 30C684961407044B004C1A8E /* CDVURLProtocol.m in Sources */, + 30A90B9314588697006178D3 /* JSONKit.m in Sources */, + 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */, + 8852C44114B65FD800F0E735 /* CDVCordovaView.m in Sources */, + 3034979E1513D56A0090E688 /* CDVLocalStorage.m in Sources */, + 3062D122151D0EDB000D9128 /* UIDevice+Extensions.m in Sources */, + 3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */, + EBA3557515ABD38C00F4DE24 /* NSArray+Comparisons.m in Sources */, + 30C5F1E015AF9E950052A00D /* CDVDevice.m in Sources */, + EB80C2AD15DEA63D004D9E7B /* CDVEcho.m in Sources */, + EB3B3548161CB44D003DBE7D /* CDVCommandQueue.m in Sources */, + EB3B357D161F2A45003DBE7D /* CDVCommandDelegateImpl.m in Sources */, + 9D76CF3D1625A4C50008A0F6 /* CDVGlobalization.m in Sources */, + 3073E9EA1656D37700957977 /* CDVInAppBrowser.m in Sources */, + F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphoneos6.*]" = ( + armv7, + armv7s, + ); + "ARCHS[sdk=iphonesimulator*]" = i386; + CLANG_ENABLE_OBJC_ARC = YES; + COPY_PHASE_STRIP = NO; + DSTROOT = "/tmp/$(PROJECT_NAME).dst"; + GCC_DYNAMIC_NO_PIC = NO; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = CordovaLib_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + PRODUCT_NAME = Cordova; + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphoneos6.*]" = ( + armv7, + armv7s, + ); + "ARCHS[sdk=iphonesimulator*]" = i386; + CLANG_ENABLE_OBJC_ARC = YES; + DSTROOT = "/tmp/$(PROJECT_NAME).dst"; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = CordovaLib_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + PRODUCT_NAME = Cordova; + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphoneos6.*]" = ( + armv7, + armv7s, + ); + "ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DDEBUG"; + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ""; + VALID_ARCHS = "i386 armv7 armv7s"; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphoneos6.*]" = ( + armv7, + armv7s, + ); + "ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + ONLY_ACTIVE_ARCH = NO; + PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "i386 armv7 armv7s"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CordovaLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CordovaLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/CordovaLib.xcscheme b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/CordovaLib.xcscheme new file mode 100644 index 0000000..590fdea --- /dev/null +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/CordovaLib.xcscheme @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0460" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "D2AAC07D0554694100DB518D" + BuildableName = "libCordova.a" + BlueprintName = "CordovaLib" + ReferencedContainer = "container:CordovaLib.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Debug"> + <Testables> + </Testables> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Release" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..283503b --- /dev/null +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>SchemeUserState</key> + <dict> + <key>CordovaLib.xcscheme</key> + <dict> + <key>orderHint</key> + <integer>1</integer> + </dict> + </dict> + <key>SuppressBuildableAutocreation</key> + <dict> + <key>D2AAC07D0554694100DB518D</key> + <dict> + <key>primary</key> + <true/> + </dict> + </dict> +</dict> +</plist> diff --git a/iPhone/CordovaLib/CordovaLib_Prefix.pch b/iPhone/CordovaLib/CordovaLib_Prefix.pch new file mode 100755 index 0000000..9545580 --- /dev/null +++ b/iPhone/CordovaLib/CordovaLib_Prefix.pch @@ -0,0 +1,22 @@ +/* + 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. + */ + +#ifdef __OBJC__ + #import <Foundation/Foundation.h> +#endif diff --git a/iPhone/CordovaLib/VERSION b/iPhone/CordovaLib/VERSION new file mode 100755 index 0000000..cc6612c --- /dev/null +++ b/iPhone/CordovaLib/VERSION @@ -0,0 +1 @@ +2.3.0
\ No newline at end of file diff --git a/iPhone/FixMyStreet.xcodeproj/project.pbxproj b/iPhone/FixMyStreet.xcodeproj/project.pbxproj index 99c2e21..9c798f1 100644 --- a/iPhone/FixMyStreet.xcodeproj/project.pbxproj +++ b/iPhone/FixMyStreet.xcodeproj/project.pbxproj @@ -7,54 +7,48 @@ objects = { /* Begin PBXBuildFile section */ - 24065F8D162C7721004574A1 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 24065F8C162C7721004574A1 /* Default-568h@2x.png */; }; - 249ED5EB162D82AD000076A1 /* cordova in Resources */ = {isa = PBXBuildFile; fileRef = 249ED5EA162D82AD000076A1 /* cordova */; }; - 249ED5EC162D83BA000076A1 /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 240FE2F1162D6E4D00250E2D /* libCordova.a */; }; - 24C01C791664F30600417E3D /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 24C01C771664F30600417E3D /* Icon.png */; }; - 24C01C7A1664F30600417E3D /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 24C01C781664F30600417E3D /* Icon@2x.png */; }; - 24C01C7C1664F33200417E3D /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 24C01C7B1664F33200417E3D /* Icon.png */; }; - 24C01C7E1664F33700417E3D /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 24C01C7D1664F33700417E3D /* Icon@2x.png */; }; - 24D3BF4C1508D60F005923FE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF4B1508D60F005923FE /* Foundation.framework */; }; - 24D3BF4E1508D60F005923FE /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF4D1508D60F005923FE /* UIKit.framework */; }; - 24D3BF501508D60F005923FE /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF4F1508D60F005923FE /* CoreGraphics.framework */; }; - 24D3BF521508D60F005923FE /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF511508D60F005923FE /* AddressBook.framework */; }; - 24D3BF541508D60F005923FE /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF531508D60F005923FE /* AddressBookUI.framework */; }; - 24D3BF561508D60F005923FE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF551508D60F005923FE /* AudioToolbox.framework */; }; - 24D3BF581508D60F005923FE /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF571508D60F005923FE /* AVFoundation.framework */; }; - 24D3BF5A1508D60F005923FE /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF591508D60F005923FE /* CoreLocation.framework */; }; - 24D3BF5C1508D60F005923FE /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF5B1508D60F005923FE /* MediaPlayer.framework */; }; - 24D3BF5E1508D60F005923FE /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF5D1508D60F005923FE /* QuartzCore.framework */; }; - 24D3BF601508D60F005923FE /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF5F1508D60F005923FE /* SystemConfiguration.framework */; }; - 24D3BF621508D60F005923FE /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF611508D60F005923FE /* MobileCoreServices.framework */; }; - 24D3BF641508D60F005923FE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24D3BF631508D60F005923FE /* CoreMedia.framework */; }; - 24D3BF6A1508D60F005923FE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF681508D60F005923FE /* InfoPlist.strings */; }; - 24D3BF6C1508D60F005923FE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D3BF6B1508D60F005923FE /* main.m */; }; - 24D3BF741508D60F005923FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF721508D60F005923FE /* Localizable.strings */; }; - 24D3BF781508D60F005923FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF761508D60F005923FE /* Localizable.strings */; }; - 24D3BF7C1508D60F005923FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF7A1508D60F005923FE /* Localizable.strings */; }; - 24D3BF801508D60F005923FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF7E1508D60F005923FE /* Localizable.strings */; }; - 24D3BF871508D60F005923FE /* icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF861508D60F005923FE /* icon-72.png */; }; - 24D3BF8A1508D60F005923FE /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF891508D60F005923FE /* Default.png */; }; - 24D3BF8C1508D60F005923FE /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF8B1508D60F005923FE /* Default@2x.png */; }; - 24D3BF8E1508D60F005923FE /* Capture.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF8D1508D60F005923FE /* Capture.bundle */; }; - 24D3BF901508D60F005923FE /* Cordova.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF8F1508D60F005923FE /* Cordova.plist */; }; - 24D3BF941508D60F005923FE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D3BF931508D60F005923FE /* AppDelegate.m */; }; - 24D3BF971508D60F005923FE /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D3BF961508D60F005923FE /* MainViewController.m */; }; - 24D3BF991508D60F005923FE /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24D3BF981508D60F005923FE /* MainViewController.xib */; }; - 24E16541150E0F9600F31308 /* www in Resources */ = {isa = PBXBuildFile; fileRef = 24E16540150E0F9600F31308 /* www */; }; + 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* AppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 1F766FE113BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDC13BBADB100FB74C0 /* Localizable.strings */; }; + 1F766FE213BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDF13BBADB100FB74C0 /* Localizable.strings */; }; + 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; + 301BF552109A68D80062928A /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF535109A57CC0062928A /* libCordova.a */; }; + 301BF570109A69640062928A /* www in Resources */ = {isa = PBXBuildFile; fileRef = 301BF56E109A69640062928A /* www */; }; + 301BF5B5109A6A2B0062928A /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B4109A6A2B0062928A /* AddressBook.framework */; }; + 301BF5B7109A6A2B0062928A /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B6109A6A2B0062928A /* AddressBookUI.framework */; }; + 301BF5B9109A6A2B0062928A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B8109A6A2B0062928A /* AudioToolbox.framework */; }; + 301BF5BB109A6A2B0062928A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BA109A6A2B0062928A /* AVFoundation.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 301BF5BD109A6A2B0062928A /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BC109A6A2B0062928A /* CFNetwork.framework */; }; + 301BF5BF109A6A2B0062928A /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BE109A6A2B0062928A /* CoreLocation.framework */; }; + 301BF5C1109A6A2B0062928A /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C0109A6A2B0062928A /* MediaPlayer.framework */; }; + 301BF5C3109A6A2B0062928A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C2109A6A2B0062928A /* QuartzCore.framework */; }; + 301BF5C5109A6A2B0062928A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C4109A6A2B0062928A /* SystemConfiguration.framework */; }; + 302D95F114D2391D003F00A1 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 302D95EF14D2391D003F00A1 /* MainViewController.m */; }; + 302D95F214D2391D003F00A1 /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 302D95F014D2391D003F00A1 /* MainViewController.xib */; }; + 305D5FD1115AB8F900A74A75 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 305D5FD0115AB8F900A74A75 /* MobileCoreServices.framework */; }; + 3072F99713A8081B00425683 /* Capture.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3072F99613A8081B00425683 /* Capture.bundle */; }; + 308D05371370CCF300D202BF /* icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D052E1370CCF300D202BF /* icon-72.png */; }; + 308D05381370CCF300D202BF /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D052F1370CCF300D202BF /* icon.png */; }; + 308D05391370CCF300D202BF /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D05301370CCF300D202BF /* icon@2x.png */; }; + 30A0434814DC770100060A13 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30A0434314DC770100060A13 /* Localizable.strings */; }; + 30A0434914DC770100060A13 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30A0434614DC770100060A13 /* Localizable.strings */; }; + 30E5649213A7FCAF007403D8 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30E5649113A7FCAF007403D8 /* CoreMedia.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + F840E1F1165FE0F500CFE078 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = F840E1F0165FE0F500CFE078 /* config.xml */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 240FE2F0162D6E4D00250E2D /* PBXContainerItemProxy */ = { + 301BF534109A57CC0062928A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 240FE2E9162D6E4D00250E2D /* CordovaLib.xcodeproj */; + containerPortal = 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 68A32D7114102E1C006B237C; + remoteGlobalIDString = D2AAC07E0554694100DB518D; remoteInfo = CordovaLib; }; - 249ED5DC162D7057000076A1 /* PBXContainerItemProxy */ = { + 301BF550109A68C00062928A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 240FE2E9162D6E4D00250E2D /* CordovaLib.xcodeproj */; + containerPortal = 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */; proxyType = 1; remoteGlobalIDString = D2AAC07D0554694100DB518D; remoteInfo = CordovaLib; @@ -62,400 +56,388 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 24065F8C162C7721004574A1 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; }; - 240FE2E9162D6E4D00250E2D /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CordovaLib.xcodeproj; path = ../../../../../libs/cordova_2.2/CordovaLib/CordovaLib.xcodeproj; sourceTree = "<group>"; }; - 249ED5EA162D82AD000076A1 /* cordova */ = {isa = PBXFileReference; lastKnownFileType = folder; path = cordova; sourceTree = "<group>"; }; - 24C01C771664F30600417E3D /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; }; - 24C01C781664F30600417E3D /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; }; - 24C01C7B1664F33200417E3D /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; }; - 24C01C7D1664F33700417E3D /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; }; - 24D3BF471508D60F005923FE /* FixMyStreet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FixMyStreet.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 24D3BF4B1508D60F005923FE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 24D3BF4D1508D60F005923FE /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - 24D3BF4F1508D60F005923FE /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 24D3BF511508D60F005923FE /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; - 24D3BF531508D60F005923FE /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; - 24D3BF551508D60F005923FE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; - 24D3BF571508D60F005923FE /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - 24D3BF591508D60F005923FE /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; - 24D3BF5B1508D60F005923FE /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; - 24D3BF5D1508D60F005923FE /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 24D3BF5F1508D60F005923FE /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; - 24D3BF611508D60F005923FE /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; - 24D3BF631508D60F005923FE /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - 24D3BF671508D60F005923FE /* FixMyStreet-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FixMyStreet-Info.plist"; sourceTree = "<group>"; }; - 24D3BF691508D60F005923FE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; - 24D3BF6B1508D60F005923FE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; - 24D3BF6D1508D60F005923FE /* FixMyStreet-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FixMyStreet-Prefix.pch"; sourceTree = "<group>"; }; - 24D3BF731508D60F005923FE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Resources/en.lproj/Localizable.strings; sourceTree = "<group>"; }; - 24D3BF771508D60F005923FE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Resources/es.lproj/Localizable.strings; sourceTree = "<group>"; }; - 24D3BF7B1508D60F005923FE /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Resources/de.lproj/Localizable.strings; sourceTree = "<group>"; }; - 24D3BF7F1508D60F005923FE /* se */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = se; path = Resources/se.lproj/Localizable.strings; sourceTree = "<group>"; }; - 24D3BF861508D60F005923FE /* icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-72.png"; path = "Resources/icons/icon-72.png"; sourceTree = "<group>"; }; - 24D3BF891508D60F005923FE /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/splash/Default.png; sourceTree = "<group>"; }; - 24D3BF8B1508D60F005923FE /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "Resources/splash/Default@2x.png"; sourceTree = "<group>"; }; - 24D3BF8D1508D60F005923FE /* Capture.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Capture.bundle; path = Resources/Capture.bundle; sourceTree = "<group>"; }; - 24D3BF8F1508D60F005923FE /* Cordova.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Cordova.plist; sourceTree = "<group>"; }; - 24D3BF921508D60F005923FE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Classes/AppDelegate.h; sourceTree = "<group>"; }; - 24D3BF931508D60F005923FE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Classes/AppDelegate.m; sourceTree = "<group>"; }; - 24D3BF951508D60F005923FE /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainViewController.h; path = Classes/MainViewController.h; sourceTree = "<group>"; }; - 24D3BF961508D60F005923FE /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MainViewController.m; path = Classes/MainViewController.m; sourceTree = "<group>"; }; - 24D3BF981508D60F005923FE /* MainViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainViewController.xib; path = Classes/MainViewController.xib; sourceTree = "<group>"; }; - 24D3BF9B1508D60F005923FE /* README */ = {isa = PBXFileReference; lastKnownFileType = text; name = README; path = Plugins/README; sourceTree = "<group>"; }; - 24E16540150E0F9600F31308 /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; name = www; path = ../www; sourceTree = "<group>"; }; + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; + 1D3623250D0F684500981E51 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; + 1D6058910D05DD3D006BFB54 /* FixMyStreet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FixMyStreet.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 1F766FDD13BBADB100FB74C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = "<group>"; }; + 1F766FE013BBADB100FB74C0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Localizable.strings; sourceTree = "<group>"; }; + 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; + 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CordovaLib.xcodeproj; path = CordovaLib/CordovaLib.xcodeproj; sourceTree = "<group>"; }; + 301BF56E109A69640062928A /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = SOURCE_ROOT; }; + 301BF5B4109A6A2B0062928A /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + 301BF5B6109A6A2B0062928A /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; + 301BF5B8109A6A2B0062928A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 301BF5BA109A6A2B0062928A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 301BF5BC109A6A2B0062928A /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + 301BF5BE109A6A2B0062928A /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + 301BF5C0109A6A2B0062928A /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; + 301BF5C2109A6A2B0062928A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 301BF5C4109A6A2B0062928A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 302D95EE14D2391D003F00A1 /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = "<group>"; }; + 302D95EF14D2391D003F00A1 /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = "<group>"; }; + 302D95F014D2391D003F00A1 /* MainViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainViewController.xib; sourceTree = "<group>"; }; + 305D5FD0115AB8F900A74A75 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 3072F99613A8081B00425683 /* Capture.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Capture.bundle; sourceTree = "<group>"; }; + 3088BBB7154F3926009F9C59 /* Default-Landscape@2x~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-Landscape@2x~ipad.png"; sourceTree = "<group>"; }; + 3088BBB8154F3926009F9C59 /* Default-Landscape~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-Landscape~ipad.png"; sourceTree = "<group>"; }; + 3088BBB9154F3926009F9C59 /* Default-Portrait@2x~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-Portrait@2x~ipad.png"; sourceTree = "<group>"; }; + 3088BBBA154F3926009F9C59 /* Default-Portrait~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-Portrait~ipad.png"; sourceTree = "<group>"; }; + 3088BBBB154F3926009F9C59 /* Default@2x~iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x~iphone.png"; sourceTree = "<group>"; }; + 3088BBBC154F3926009F9C59 /* Default~iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default~iphone.png"; sourceTree = "<group>"; }; + 308D052E1370CCF300D202BF /* icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-72.png"; sourceTree = "<group>"; }; + 308D052F1370CCF300D202BF /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; }; + 308D05301370CCF300D202BF /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon@2x.png"; sourceTree = "<group>"; }; + 30A0434414DC770100060A13 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Localizable.strings; sourceTree = "<group>"; }; + 30A0434714DC770100060A13 /* se */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = se; path = Localizable.strings; sourceTree = "<group>"; }; + 30E5649113A7FCAF007403D8 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + 32CA4F630368D1EE00C91783 /* FixMyStreet-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FixMyStreet-Prefix.pch"; sourceTree = "<group>"; }; + 8D1107310486CEB800E47090 /* FixMyStreet-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "FixMyStreet-Info.plist"; path = "../FixMyStreet-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = "<group>"; }; + D4A0D8751607E02300AEF8BB /* Default-568h@2x~iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x~iphone.png"; sourceTree = "<group>"; }; + F840E1F0165FE0F500CFE078 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = config.xml; path = FixMyStreet/config.xml; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 24D3BF411508D60F005923FE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 249ED5EC162D83BA000076A1 /* libCordova.a in Frameworks */, - 24D3BF4C1508D60F005923FE /* Foundation.framework in Frameworks */, - 24D3BF4E1508D60F005923FE /* UIKit.framework in Frameworks */, - 24D3BF501508D60F005923FE /* CoreGraphics.framework in Frameworks */, - 24D3BF521508D60F005923FE /* AddressBook.framework in Frameworks */, - 24D3BF541508D60F005923FE /* AddressBookUI.framework in Frameworks */, - 24D3BF561508D60F005923FE /* AudioToolbox.framework in Frameworks */, - 24D3BF581508D60F005923FE /* AVFoundation.framework in Frameworks */, - 24D3BF5A1508D60F005923FE /* CoreLocation.framework in Frameworks */, - 24D3BF5C1508D60F005923FE /* MediaPlayer.framework in Frameworks */, - 24D3BF5E1508D60F005923FE /* QuartzCore.framework in Frameworks */, - 24D3BF601508D60F005923FE /* SystemConfiguration.framework in Frameworks */, - 24D3BF621508D60F005923FE /* MobileCoreServices.framework in Frameworks */, - 24D3BF641508D60F005923FE /* CoreMedia.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 24D3BF441508D60F005923FE /* Frameworks */ = { + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 301BF552109A68D80062928A /* libCordova.a in Frameworks */, + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */, + 301BF5B5109A6A2B0062928A /* AddressBook.framework in Frameworks */, + 301BF5B7109A6A2B0062928A /* AddressBookUI.framework in Frameworks */, + 301BF5B9109A6A2B0062928A /* AudioToolbox.framework in Frameworks */, + 301BF5BB109A6A2B0062928A /* AVFoundation.framework in Frameworks */, + 301BF5BD109A6A2B0062928A /* CFNetwork.framework in Frameworks */, + 301BF5BF109A6A2B0062928A /* CoreLocation.framework in Frameworks */, + 301BF5C1109A6A2B0062928A /* MediaPlayer.framework in Frameworks */, + 301BF5C3109A6A2B0062928A /* QuartzCore.framework in Frameworks */, + 301BF5C5109A6A2B0062928A /* SystemConfiguration.framework in Frameworks */, + 305D5FD1115AB8F900A74A75 /* MobileCoreServices.framework in Frameworks */, + 30E5649213A7FCAF007403D8 /* CoreMedia.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 240FE2EA162D6E4D00250E2D /* Products */ = { - isa = PBXGroup; - children = ( - 240FE2F1162D6E4D00250E2D /* libCordova.a */, - ); - name = Products; - sourceTree = "<group>"; - }; - 24D3BF391508D60E005923FE = { + 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( - 24C01C7D1664F33700417E3D /* Icon@2x.png */, - 24C01C7B1664F33200417E3D /* Icon.png */, - 249ED5EA162D82AD000076A1 /* cordova */, - 24065F8C162C7721004574A1 /* Default-568h@2x.png */, - 24E16540150E0F9600F31308 /* www */, - 24D3BF651508D60F005923FE /* FixMyStreet */, - 24D3BF4A1508D60F005923FE /* Frameworks */, - 24D3BF481508D60F005923FE /* Products */, - 240FE2E9162D6E4D00250E2D /* CordovaLib.xcodeproj */, + 302D95EE14D2391D003F00A1 /* MainViewController.h */, + 302D95EF14D2391D003F00A1 /* MainViewController.m */, + 302D95F014D2391D003F00A1 /* MainViewController.xib */, + 1D3623240D0F684500981E51 /* AppDelegate.h */, + 1D3623250D0F684500981E51 /* AppDelegate.m */, ); - sourceTree = "<group>"; + name = Classes; + path = FixMyStreet/Classes; + sourceTree = SOURCE_ROOT; }; - 24D3BF481508D60F005923FE /* Products */ = { + 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( - 24D3BF471508D60F005923FE /* FixMyStreet.app */, + 1D6058910D05DD3D006BFB54 /* FixMyStreet.app */, ); name = Products; sourceTree = "<group>"; }; - 24D3BF4A1508D60F005923FE /* Frameworks */ = { + 1F766FDB13BBADB100FB74C0 /* en.lproj */ = { isa = PBXGroup; children = ( - 24D3BF4B1508D60F005923FE /* Foundation.framework */, - 24D3BF4D1508D60F005923FE /* UIKit.framework */, - 24D3BF4F1508D60F005923FE /* CoreGraphics.framework */, - 24D3BF511508D60F005923FE /* AddressBook.framework */, - 24D3BF531508D60F005923FE /* AddressBookUI.framework */, - 24D3BF551508D60F005923FE /* AudioToolbox.framework */, - 24D3BF571508D60F005923FE /* AVFoundation.framework */, - 24D3BF591508D60F005923FE /* CoreLocation.framework */, - 24D3BF5B1508D60F005923FE /* MediaPlayer.framework */, - 24D3BF5D1508D60F005923FE /* QuartzCore.framework */, - 24D3BF5F1508D60F005923FE /* SystemConfiguration.framework */, - 24D3BF611508D60F005923FE /* MobileCoreServices.framework */, - 24D3BF631508D60F005923FE /* CoreMedia.framework */, + 1F766FDC13BBADB100FB74C0 /* Localizable.strings */, ); - name = Frameworks; + path = en.lproj; sourceTree = "<group>"; }; - 24D3BF651508D60F005923FE /* FixMyStreet */ = { + 1F766FDE13BBADB100FB74C0 /* es.lproj */ = { isa = PBXGroup; children = ( - 24D3BF701508D60F005923FE /* Resources */, - 24D3BF911508D60F005923FE /* Classes */, - 24D3BF9A1508D60F005923FE /* Plugins */, - 24D3BF661508D60F005923FE /* Supporting Files */, + 1F766FDF13BBADB100FB74C0 /* Localizable.strings */, ); - path = FixMyStreet; + path = es.lproj; sourceTree = "<group>"; }; - 24D3BF661508D60F005923FE /* Supporting Files */ = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( - 24D3BF671508D60F005923FE /* FixMyStreet-Info.plist */, - 24D3BF681508D60F005923FE /* InfoPlist.strings */, - 24D3BF6B1508D60F005923FE /* main.m */, - 24D3BF6D1508D60F005923FE /* FixMyStreet-Prefix.pch */, - 24D3BF8F1508D60F005923FE /* Cordova.plist */, - 24D3BF981508D60F005923FE /* MainViewController.xib */, - ); - name = "Supporting Files"; + F840E1F0165FE0F500CFE078 /* config.xml */, + 301BF56E109A69640062928A /* www */, + 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */, + 080E96DDFE201D6D7F000001 /* Classes */, + 307C750510C5A3420062BCA9 /* Plugins */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; sourceTree = "<group>"; }; - 24D3BF701508D60F005923FE /* Resources */ = { + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( - 24D3BF8D1508D60F005923FE /* Capture.bundle */, - 24D3BF711508D60F005923FE /* en.lproj */, - 24D3BF751508D60F005923FE /* es.lproj */, - 24D3BF791508D60F005923FE /* de.lproj */, - 24D3BF7D1508D60F005923FE /* se.lproj */, - 24D3BF811508D60F005923FE /* icons */, - 24D3BF881508D60F005923FE /* splash */, + 32CA4F630368D1EE00C91783 /* FixMyStreet-Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, ); - name = Resources; + name = "Other Sources"; + path = FixMyStreet; sourceTree = "<group>"; }; - 24D3BF711508D60F005923FE /* en.lproj */ = { + 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( - 24D3BF721508D60F005923FE /* Localizable.strings */, + 30A0434214DC770100060A13 /* de.lproj */, + 30A0434514DC770100060A13 /* se.lproj */, + 1F766FDB13BBADB100FB74C0 /* en.lproj */, + 1F766FDE13BBADB100FB74C0 /* es.lproj */, + 3072F99613A8081B00425683 /* Capture.bundle */, + 308D052D1370CCF300D202BF /* icons */, + 308D05311370CCF300D202BF /* splash */, + 8D1107310486CEB800E47090 /* FixMyStreet-Info.plist */, ); - name = en.lproj; + name = Resources; + path = FixMyStreet/Resources; sourceTree = "<group>"; }; - 24D3BF751508D60F005923FE /* es.lproj */ = { + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( - 24D3BF761508D60F005923FE /* Localizable.strings */, + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 288765FC0DF74451002DB57D /* CoreGraphics.framework */, + 301BF5B4109A6A2B0062928A /* AddressBook.framework */, + 301BF5B6109A6A2B0062928A /* AddressBookUI.framework */, + 301BF5B8109A6A2B0062928A /* AudioToolbox.framework */, + 301BF5BA109A6A2B0062928A /* AVFoundation.framework */, + 301BF5BC109A6A2B0062928A /* CFNetwork.framework */, + 301BF5BE109A6A2B0062928A /* CoreLocation.framework */, + 301BF5C0109A6A2B0062928A /* MediaPlayer.framework */, + 301BF5C2109A6A2B0062928A /* QuartzCore.framework */, + 301BF5C4109A6A2B0062928A /* SystemConfiguration.framework */, + 305D5FD0115AB8F900A74A75 /* MobileCoreServices.framework */, + 30E5649113A7FCAF007403D8 /* CoreMedia.framework */, ); - name = es.lproj; + name = Frameworks; sourceTree = "<group>"; }; - 24D3BF791508D60F005923FE /* de.lproj */ = { + 301BF52E109A57CC0062928A /* Products */ = { isa = PBXGroup; children = ( - 24D3BF7A1508D60F005923FE /* Localizable.strings */, + 301BF535109A57CC0062928A /* libCordova.a */, ); - name = de.lproj; + name = Products; sourceTree = "<group>"; }; - 24D3BF7D1508D60F005923FE /* se.lproj */ = { + 307C750510C5A3420062BCA9 /* Plugins */ = { isa = PBXGroup; children = ( - 24D3BF7E1508D60F005923FE /* Localizable.strings */, ); - name = se.lproj; - sourceTree = "<group>"; + name = Plugins; + path = FixMyStreet/Plugins; + sourceTree = SOURCE_ROOT; }; - 24D3BF811508D60F005923FE /* icons */ = { + 308D052D1370CCF300D202BF /* icons */ = { isa = PBXGroup; children = ( - 24C01C771664F30600417E3D /* Icon.png */, - 24C01C781664F30600417E3D /* Icon@2x.png */, - 24D3BF861508D60F005923FE /* icon-72.png */, + 308D052E1370CCF300D202BF /* icon-72.png */, + 308D052F1370CCF300D202BF /* icon.png */, + 308D05301370CCF300D202BF /* icon@2x.png */, ); - name = icons; + path = icons; sourceTree = "<group>"; }; - 24D3BF881508D60F005923FE /* splash */ = { + 308D05311370CCF300D202BF /* splash */ = { isa = PBXGroup; children = ( - 24D3BF891508D60F005923FE /* Default.png */, - 24D3BF8B1508D60F005923FE /* Default@2x.png */, - ); - name = splash; + D4A0D8751607E02300AEF8BB /* Default-568h@2x~iphone.png */, + 3088BBB7154F3926009F9C59 /* Default-Landscape@2x~ipad.png */, + 3088BBB8154F3926009F9C59 /* Default-Landscape~ipad.png */, + 3088BBB9154F3926009F9C59 /* Default-Portrait@2x~ipad.png */, + 3088BBBA154F3926009F9C59 /* Default-Portrait~ipad.png */, + 3088BBBB154F3926009F9C59 /* Default@2x~iphone.png */, + 3088BBBC154F3926009F9C59 /* Default~iphone.png */, + ); + path = splash; sourceTree = "<group>"; }; - 24D3BF911508D60F005923FE /* Classes */ = { + 30A0434214DC770100060A13 /* de.lproj */ = { isa = PBXGroup; children = ( - 24D3BF921508D60F005923FE /* AppDelegate.h */, - 24D3BF931508D60F005923FE /* AppDelegate.m */, - 24D3BF951508D60F005923FE /* MainViewController.h */, - 24D3BF961508D60F005923FE /* MainViewController.m */, + 30A0434314DC770100060A13 /* Localizable.strings */, ); - name = Classes; + path = de.lproj; sourceTree = "<group>"; }; - 24D3BF9A1508D60F005923FE /* Plugins */ = { + 30A0434514DC770100060A13 /* se.lproj */ = { isa = PBXGroup; children = ( - 24D3BF9B1508D60F005923FE /* README */, + 30A0434614DC770100060A13 /* Localizable.strings */, ); - name = Plugins; + path = se.lproj; sourceTree = "<group>"; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 24D3BF461508D60F005923FE /* FixMyStreet */ = { + 1D6058900D05DD3D006BFB54 /* FixMyStreet */ = { isa = PBXNativeTarget; - buildConfigurationList = 24D3BFA01508D60F005923FE /* Build configuration list for PBXNativeTarget "FixMyStreet" */; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "FixMyStreet" */; buildPhases = ( - 24D3BF401508D60F005923FE /* Sources */, - 24D3BF411508D60F005923FE /* Frameworks */, - 24D3BF421508D60F005923FE /* Resources */, - 24D3BF431508D60F005923FE /* Sources */, - 24D3BF441508D60F005923FE /* Frameworks */, + 304B58A110DAC018002A0835 /* Touch www folder */, + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, ); buildRules = ( ); dependencies = ( - 249ED5DD162D7057000076A1 /* PBXTargetDependency */, + 301BF551109A68C00062928A /* PBXTargetDependency */, ); name = FixMyStreet; productName = FixMyStreet; - productReference = 24D3BF471508D60F005923FE /* FixMyStreet.app */; + productReference = 1D6058910D05DD3D006BFB54 /* FixMyStreet.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 24D3BF3B1508D60E005923FE /* Project object */ = { + 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0450; + LastUpgradeCheck = 0430; }; - buildConfigurationList = 24D3BF3E1508D60E005923FE /* Build configuration list for PBXProject "FixMyStreet" */; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "FixMyStreet" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; - hasScannedForEncodings = 0; + hasScannedForEncodings = 1; knownRegions = ( + English, + Japanese, + French, + German, en, es, de, se, ); - mainGroup = 24D3BF391508D60E005923FE; - productRefGroup = 24D3BF481508D60F005923FE /* Products */; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; projectDirPath = ""; projectReferences = ( { - ProductGroup = 240FE2EA162D6E4D00250E2D /* Products */; - ProjectRef = 240FE2E9162D6E4D00250E2D /* CordovaLib.xcodeproj */; + ProductGroup = 301BF52E109A57CC0062928A /* Products */; + ProjectRef = 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */; }, ); projectRoot = ""; targets = ( - 24D3BF461508D60F005923FE /* FixMyStreet */, + 1D6058900D05DD3D006BFB54 /* FixMyStreet */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 240FE2F1162D6E4D00250E2D /* libCordova.a */ = { + 301BF535109A57CC0062928A /* libCordova.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libCordova.a; - remoteRef = 240FE2F0162D6E4D00250E2D /* PBXContainerItemProxy */; + remoteRef = 301BF534109A57CC0062928A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - 24D3BF421508D60F005923FE /* Resources */ = { + 1D60588D0D05DD3D006BFB54 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 24D3BF6A1508D60F005923FE /* InfoPlist.strings in Resources */, - 24D3BF741508D60F005923FE /* Localizable.strings in Resources */, - 24D3BF781508D60F005923FE /* Localizable.strings in Resources */, - 24D3BF7C1508D60F005923FE /* Localizable.strings in Resources */, - 24D3BF801508D60F005923FE /* Localizable.strings in Resources */, - 24D3BF871508D60F005923FE /* icon-72.png in Resources */, - 24D3BF8A1508D60F005923FE /* Default.png in Resources */, - 24D3BF8C1508D60F005923FE /* Default@2x.png in Resources */, - 24D3BF8E1508D60F005923FE /* Capture.bundle in Resources */, - 24D3BF901508D60F005923FE /* Cordova.plist in Resources */, - 24D3BF991508D60F005923FE /* MainViewController.xib in Resources */, - 24E16541150E0F9600F31308 /* www in Resources */, - 24065F8D162C7721004574A1 /* Default-568h@2x.png in Resources */, - 249ED5EB162D82AD000076A1 /* cordova in Resources */, - 24C01C791664F30600417E3D /* Icon.png in Resources */, - 24C01C7A1664F30600417E3D /* Icon@2x.png in Resources */, - 24C01C7C1664F33200417E3D /* Icon.png in Resources */, - 24C01C7E1664F33700417E3D /* Icon@2x.png in Resources */, + F840E1F1165FE0F500CFE078 /* config.xml in Resources */, + 301BF570109A69640062928A /* www in Resources */, + 308D05371370CCF300D202BF /* icon-72.png in Resources */, + 308D05381370CCF300D202BF /* icon.png in Resources */, + 308D05391370CCF300D202BF /* icon@2x.png in Resources */, + 3072F99713A8081B00425683 /* Capture.bundle in Resources */, + 1F766FE113BBADB100FB74C0 /* Localizable.strings in Resources */, + 1F766FE213BBADB100FB74C0 /* Localizable.strings in Resources */, + 302D95F214D2391D003F00A1 /* MainViewController.xib in Resources */, + 30A0434814DC770100060A13 /* Localizable.strings in Resources */, + 30A0434914DC770100060A13 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXSourcesBuildPhase section */ - 24D3BF401508D60F005923FE /* Sources */ = { - isa = PBXSourcesBuildPhase; +/* Begin PBXShellScriptBuildPhase section */ + 304B58A110DAC018002A0835 /* Touch www folder */ = { + isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( - 24D3BF6C1508D60F005923FE /* main.m in Sources */, - 24D3BF941508D60F005923FE /* AppDelegate.m in Sources */, - 24D3BF971508D60F005923FE /* MainViewController.m in Sources */, + ); + inputPaths = ( + ); + name = "Touch www folder"; + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "touch -cm ${PROJECT_DIR}/www"; }; - 24D3BF431508D60F005923FE /* Sources */ = { +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */, + 302D95F114D2391D003F00A1 /* MainViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 249ED5DD162D7057000076A1 /* PBXTargetDependency */ = { + 301BF551109A68C00062928A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = CordovaLib; - targetProxy = 249ED5DC162D7057000076A1 /* PBXContainerItemProxy */; + targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 24D3BF681508D60F005923FE /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 24D3BF691508D60F005923FE /* en */, - ); - name = InfoPlist.strings; - sourceTree = "<group>"; - }; - 24D3BF721508D60F005923FE /* Localizable.strings */ = { + 1F766FDC13BBADB100FB74C0 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 24D3BF731508D60F005923FE /* en */, + 1F766FDD13BBADB100FB74C0 /* en */, ); name = Localizable.strings; sourceTree = "<group>"; }; - 24D3BF761508D60F005923FE /* Localizable.strings */ = { + 1F766FDF13BBADB100FB74C0 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 24D3BF771508D60F005923FE /* es */, + 1F766FE013BBADB100FB74C0 /* es */, ); name = Localizable.strings; sourceTree = "<group>"; }; - 24D3BF7A1508D60F005923FE /* Localizable.strings */ = { + 30A0434314DC770100060A13 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 24D3BF7B1508D60F005923FE /* de */, + 30A0434414DC770100060A13 /* de */, ); name = Localizable.strings; sourceTree = "<group>"; }; - 24D3BF7E1508D60F005923FE /* Localizable.strings */ = { + 30A0434614DC770100060A13 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 24D3BF7F1508D60F005923FE /* se */, + 30A0434714DC770100060A13 /* se */, ); name = Localizable.strings; sourceTree = "<group>"; @@ -463,157 +445,136 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 24D3BF9E1508D60F005923FE /* Debug */ = { + 1D6058940D05DD3E006BFB54 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; CLANG_ENABLE_OBJC_ARC = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CLANG_WARN_OBJCPP_ARC_ABI = YES; COPY_PHASE_STRIP = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "FixMyStreet/FixMyStreet-Prefix.pch"; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.0; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; + INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + PRODUCT_NAME = FixMyStreet; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 24D3BF9F1508D60F005923FE /* Release */ = { + 1D6058950D05DD3E006BFB54 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; CLANG_ENABLE_OBJC_ARC = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CLANG_WARN_OBJCPP_ARC_ABI = YES; COPY_PHASE_STRIP = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "FixMyStreet/FixMyStreet-Prefix.pch"; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.0; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; + INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + PRODUCT_NAME = FixMyStreet; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 24D3BFA11508D60F005923FE /* Debug */ = { + C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = /Users/Shared/Cordova/Frameworks; - GCC_DYNAMIC_NO_PIC = NO; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "FixMyStreet/FixMyStreet-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1,"; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; GCC_THUMB_SUPPORT = NO; - "HEADER_SEARCH_PATHS[arch=*]" = ( + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"", "\"$(OBJROOT)/UninstalledProducts/include\"", "\"$(BUILT_PRODUCTS_DIR)\"", ); - INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; OTHER_LDFLAGS = ( "-weak_framework", + CoreFoundation, + "-weak_framework", UIKit, "-weak_framework", AVFoundation, "-weak_framework", CoreMedia, - "-weak_library", - /usr/lib/libSystem.B.dylib, - "-Obj-C", + "-weak-lSystem", "-all_load", - "-weak_framework", - CoreFoundation, + "-Obj-C", ); - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 1; - VALIDATE_PRODUCT = YES; - WRAPPER_EXTENSION = app; + SDKROOT = iphoneos; + SKIP_INSTALL = NO; + USER_HEADER_SEARCH_PATHS = ""; }; name = Debug; }; - 24D3BFA21508D60F005923FE /* Release */ = { + C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = /Users/Shared/Cordova/Frameworks; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "FixMyStreet/FixMyStreet-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "NDEBUG=1,", - "CORDOVA_FRAMEWORK=1", - ); + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; GCC_THUMB_SUPPORT = NO; - "HEADER_SEARCH_PATHS[arch=*]" = ( + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"", "\"$(OBJROOT)/UninstalledProducts/include\"", "\"$(BUILT_PRODUCTS_DIR)\"", ); - INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; OTHER_LDFLAGS = ( "-weak_framework", + CoreFoundation, + "-weak_framework", UIKit, "-weak_framework", AVFoundation, "-weak_framework", CoreMedia, - "-weak_library", - /usr/lib/libSystem.B.dylib, - "-Obj-C", + "-weak-lSystem", "-all_load", - "-weak_framework", - CoreFoundation, + "-Obj-C", ); - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 1; - VALIDATE_PRODUCT = NO; - WRAPPER_EXTENSION = app; + SDKROOT = iphoneos; + SKIP_INSTALL = NO; + USER_HEADER_SEARCH_PATHS = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 24D3BF3E1508D60E005923FE /* Build configuration list for PBXProject "FixMyStreet" */ = { + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "FixMyStreet" */ = { isa = XCConfigurationList; buildConfigurations = ( - 24D3BF9E1508D60F005923FE /* Debug */, - 24D3BF9F1508D60F005923FE /* Release */, + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 24D3BFA01508D60F005923FE /* Build configuration list for PBXNativeTarget "FixMyStreet" */ = { + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "FixMyStreet" */ = { isa = XCConfigurationList; buildConfigurations = ( - 24D3BFA11508D60F005923FE /* Debug */, - 24D3BFA21508D60F005923FE /* Release */, + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = 24D3BF3B1508D60E005923FE /* Project object */; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } diff --git a/iPhone/FixMyStreet/Classes/MainViewController.h b/iPhone/FixMyStreet/Classes/MainViewController.h index c2b68de..e483f72 100644 --- a/iPhone/FixMyStreet/Classes/MainViewController.h +++ b/iPhone/FixMyStreet/Classes/MainViewController.h @@ -6,9 +6,9 @@ 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 @@ -19,14 +19,22 @@ // // MainViewController.h -// cordova1.8.0 +// tmp_ios // -// Created by Struan Donald on 08/06/2012. -// Copyright __MyCompanyName__ 2012. All rights reserved. +// Created by ___FULLUSERNAME___ on ___DATE___. +// Copyright ___ORGANIZATIONNAME___ ___YEAR___. All rights reserved. // #import <Cordova/CDVViewController.h> +#import <Cordova/CDVCommandDelegateImpl.h> +#import <Cordova/CDVCommandQueue.h> @interface MainViewController : CDVViewController @end + +@interface MainCommandDelegate : CDVCommandDelegateImpl +@end + +@interface MainCommandQueue : CDVCommandQueue +@end diff --git a/iPhone/FixMyStreet/Classes/MainViewController.m b/iPhone/FixMyStreet/Classes/MainViewController.m index 0df74a0..7d3871b 100644 --- a/iPhone/FixMyStreet/Classes/MainViewController.m +++ b/iPhone/FixMyStreet/Classes/MainViewController.m @@ -19,7 +19,7 @@ // // MainViewController.h -// cordova-2.2 +// tmp_ios // // Created by ___FULLUSERNAME___ on ___DATE___. // Copyright ___ORGANIZATIONNAME___ ___YEAR___. All rights reserved. @@ -33,7 +33,22 @@ { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { - // Custom initialization + // Uncomment to override the CDVCommandDelegateImpl used + // _commandDelegate = [[MainCommandDelegate alloc] initWithViewController:self]; + // Uncomment to override the CDVCommandQueue used + // _commandQueue = [[MainCommandQueue alloc] initWithViewController:self]; + } + return self; +} + +- (id)init +{ + self = [super init]; + if (self) { + // Uncomment to override the CDVCommandDelegateImpl used + // _commandDelegate = [[MainCommandDelegate alloc] initWithViewController:self]; + // Uncomment to override the CDVCommandQueue used + // _commandQueue = [[MainCommandQueue alloc] initWithViewController:self]; } return self; } @@ -46,19 +61,12 @@ // Release any cached data, images, etc that aren't in use. } -#pragma mark - View lifecycle +#pragma mark View lifecycle - (void)viewWillAppear:(BOOL)animated { - // Set the main view to utilize the entire application frame space of the device. - // Change this to suit your view's UI footprint needs in your application. - - UIView* rootView = [[[[UIApplication sharedApplication] keyWindow] rootViewController] view]; - CGRect webViewFrame = [[[rootView subviews] objectAtIndex:0] frame]; // first subview is the UIWebView - - if (CGRectEqualToRect(webViewFrame, CGRectZero)) { // UIWebView is sized according to its parent, here it hasn't been sized yet - self.view.frame = [[UIScreen mainScreen] applicationFrame]; // size UIWebView's parent according to application frame, which will in turn resize the UIWebView - } + // View defaults to full size. If you want to customize the view's size, or its subviews (e.g. webView), + // you can do so here. [super viewWillAppear:animated]; } @@ -91,44 +99,10 @@ } */ -/* Comment out the block below to over-ride */ - -/* -#pragma CDVCommandDelegate implementation - -- (id) getCommandInstance:(NSString*)className -{ - return [super getCommandInstance:className]; -} - -- (BOOL) execute:(CDVInvokedUrlCommand*)command -{ - return [super execute:command]; -} - -- (NSString*) pathForResource:(NSString*)resourcepath; -{ - return [super pathForResource:resourcepath]; -} - -- (void) registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className -{ - return [super registerPlugin:plugin withClassName:className]; -} -*/ - -#pragma UIWebDelegate implementation +#pragma mark UIWebDelegate implementation - (void)webViewDidFinishLoad:(UIWebView*)theWebView { - // only valid if ___PROJECTNAME__-Info.plist specifies a protocol to handle - if (self.invokeString) { - // this is passed before the deviceready event is fired, so you can access it in js when you receive deviceready - NSLog(@"DEPRECATED: window.invokeString - use the window.handleOpenURL(url) function instead, which is always called when the app is launched through a custom scheme url."); - NSString* jsString = [NSString stringWithFormat:@"var invokeString = \"%@\";", self.invokeString]; - [theWebView stringByEvaluatingJavaScriptFromString:jsString]; - } - // Black base color for background matches the native apps theWebView.backgroundColor = [UIColor blackColor]; @@ -156,3 +130,45 @@ */ @end + +@implementation MainCommandDelegate + +/* To override the methods, uncomment the line in the init function(s) + in MainViewController.m + */ + +#pragma mark CDVCommandDelegate implementation + +- (id)getCommandInstance:(NSString*)className +{ + return [super getCommandInstance:className]; +} + +/* + NOTE: this will only inspect execute calls coming explicitly from native plugins, + not the commandQueue (from JavaScript). To see execute calls from JavaScript, see + MainCommandQueue below +*/ +- (BOOL)execute:(CDVInvokedUrlCommand*)command +{ + return [super execute:command]; +} + +- (NSString*)pathForResource:(NSString*)resourcepath; +{ + return [super pathForResource:resourcepath]; +} + +@end + +@implementation MainCommandQueue + +/* To override, uncomment the line in the init function(s) + in MainViewController.m + */ +- (BOOL)execute:(CDVInvokedUrlCommand*)command +{ + return [super execute:command]; +} + +@end diff --git a/iPhone/FixMyStreet/Cordova.plist b/iPhone/FixMyStreet/Cordova.plist deleted file mode 100644 index cc71225..0000000 --- a/iPhone/FixMyStreet/Cordova.plist +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>UIWebViewBounce</key> - <true/> - <key>TopActivityIndicator</key> - <string>gray</string> - <key>EnableLocation</key> - <false/> - <key>EnableViewportScale</key> - <false/> - <key>AutoHideSplashScreen</key> - <true/> - <key>ShowSplashScreenSpinner</key> - <true/> - <key>MediaPlaybackRequiresUserAction</key> - <false/> - <key>AllowInlineMediaPlayback</key> - <false/> - <key>OpenAllWhitelistURLsInWebView</key> - <false/> - <key>ExternalHosts</key> - <array> - <string>*.tilma.mysociety.org</string> - <string>mapit.mysociety.org</string> - <string>*.tile.openstreetmap.org</string> - <string>*.virtualearth.net</string> - <string>struan.fixmystreet.dev.mysociety.org</string> - </array> - <key>Plugins</key> - <dict> - <key>Device</key> - <string>CDVDevice</string> - <key>Compass</key> - <string>CDVLocation</string> - <key>Accelerometer</key> - <string>CDVAccelerometer</string> - <key>Camera</key> - <string>CDVCamera</string> - <key>NetworkStatus</key> - <string>CDVConnection</string> - <key>Contacts</key> - <string>CDVContacts</string> - <key>Debug Console</key> - <string>CDVDebugConsole</string> - <key>File</key> - <string>CDVFile</string> - <key>FileTransfer</key> - <string>CDVFileTransfer</string> - <key>Geolocation</key> - <string>CDVLocation</string> - <key>Notification</key> - <string>CDVNotification</string> - <key>Media</key> - <string>CDVSound</string> - <key>Capture</key> - <string>CDVCapture</string> - <key>SplashScreen</key> - <string>CDVSplashScreen</string> - <key>Battery</key> - <string>CDVBattery</string> - </dict> -</dict> -</plist> diff --git a/iPhone/FixMyStreet/config.xml b/iPhone/FixMyStreet/config.xml new file mode 100644 index 0000000..ae3321a --- /dev/null +++ b/iPhone/FixMyStreet/config.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<cordova> + <preference name="AllowInlineMediaPlayback" value="false"/> + <preference name="AutoHideSplashScreen" value="true"/> + <preference name="EnableLocation" value="false"/> + <preference name="EnableViewportScale" value="false"/> + <preference name="MediaPlaybackRequiresUserAction" value="false"/> + <preference name="OpenAllWhitelistURLsInWebView" value="false"/> + <preference name="ShowSplashScreenSpinner" value="true"/> + <preference name="TopActivityIndicator" value="gray"/> + <preference name="UIWebViewBounce" value="true"/> + <plugins> + <plugin name="Accelerometer" value="CDVAccelerometer"/> + <plugin name="Battery" value="CDVBattery"/> + <plugin name="Camera" value="CDVCamera"/> + <plugin name="Capture" value="CDVCapture"/> + <plugin name="Compass" value="CDVLocation"/> + <plugin name="Contacts" value="CDVContacts"/> + <plugin name="Debug Console" value="CDVDebugConsole"/> + <plugin name="Device" value="CDVDevice"/> + <plugin name="File" value="CDVFile"/> + <plugin name="FileTransfer" value="CDVFileTransfer"/> + <plugin name="Geolocation" value="CDVLocation"/> + <plugin name="Media" value="CDVSound"/> + <plugin name="NetworkStatus" value="CDVConnection"/> + <plugin name="Notification" value="CDVNotification"/> + <plugin name="SplashScreen" value="CDVSplashScreen"/> + </plugins> + <access origin="*.tile.openstreetmap.org"/> + <access origin="*.tilma.mysociety.org"/> + <access origin="*.virtualearth.net"/> + <access origin="mapit.mysociety.org"/> + <access origin="struan.fixmystreet.dev.mysociety.org"/> +</cordova> diff --git a/www/cordova-independent.js b/www/cordova-independent.js index 05fd645..b109a94 100644 --- a/www/cordova-independent.js +++ b/www/cordova-independent.js @@ -2,7 +2,7 @@ var scriptElement = document.createElement("script"); scriptElement.type = "text/javascript"; if (navigator.userAgent.match(/(iPhone|iPod|iPad)/)) { - scriptElement.src = 'cordova-ios-2.2.0.js'; + scriptElement.src = 'cordova-ios-2.3.0.js'; } else if (navigator.userAgent.match(/Android/)) { scriptElement.src = 'cordova-android-2.2.0.js'; } else { diff --git a/www/cordova-ios-2.3.0.js b/www/cordova-ios-2.3.0.js new file mode 100755 index 0000000..a8e5f6b --- /dev/null +++ b/www/cordova-ios-2.3.0.js @@ -0,0 +1,6032 @@ +// commit 24d65ab645742e8360c3dd16d7a36411cc3383e0 + +// File generated at :: Thu Jan 03 2013 16:57:26 GMT-0800 (PST) + +/* + 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. +*/ + +;(function() { + +// file: lib/scripts/require.js + +var require, + define; + +(function () { + var modules = {}; + // Stack of moduleIds currently being built. + var requireStack = []; + // Map of module ID -> index into requireStack of modules currently being built. + var inProgressModules = {}; + + function build(module) { + var factory = module.factory; + module.exports = {}; + delete module.factory; + factory(require, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw "module " + id + " not found"; + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw "Cycle in require graph: " + cycle; + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (modules[id]) { + throw "module " + id + " already defined"; + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + +})(); + +//Export for use in node +if (typeof module === "object" && typeof require === "function") { + module.exports.require = require; + module.exports.define = define; +} + +// file: lib/cordova.js +define("cordova", function(require, exports, module) { + + +var channel = require('cordova/channel'); + +/** + * Listen for DOMContentLoaded and notify our channel subscribers. + */ +document.addEventListener('DOMContentLoaded', function() { + channel.onDOMContentLoaded.fire(); +}, false); +if (document.readyState == 'complete' || document.readyState == 'interactive') { + channel.onDOMContentLoaded.fire(); +} + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}, + windowEventHandlers = {}; + +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] != 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] != 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] != "undefined") { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] != "undefined") { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent(type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + } + return event; +} + +if(typeof window.console === "undefined") { + window.console = { + log:function(){} + }; +} + +var cordova = { + define:define, + require:require, + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler:function(event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler:function(event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler:function(event) { + delete documentEventHandlers[event]; + }, + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function() { + return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, + 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; + }, + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function(type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] != 'undefined') { + if( bNoDetach ) { + documentEventHandlers[type].fire(evt); + } + else { + setTimeout(function() { + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + fireWindowEvent: function(type, data) { + var evt = createEvent(type,data); + if (typeof windowEventHandlers[type] != 'undefined') { + setTimeout(function() { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + callbackSuccess: function(callbackId, args) { + try { + cordova.callbackFromNative(callbackId, true, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning error result from an action. + */ + callbackError: function(callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + try { + cordova.callbackFromNative(callbackId, false, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function(callbackId, success, status, message, keepCallback) { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (success && status == cordova.callbackStatus.OK) { + callback.success && callback.success(message); + } else if (!success) { + callback.fail && callback.fail(message); + } + + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + }, + addConstructor: function(func) { + channel.onCordovaReady.subscribe(function() { + try { + func(); + } catch(e) { + console.log("Failed to run constructor: " + e); + } + }); + } +}; + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +module.exports = cordova; + +}); + +// file: lib/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var exec = require('cordova/exec'); +var moduleExports = module.exports; + +var typeMap = { + 'A': 'Array', + 'D': 'Date', + 'N': 'Number', + 'S': 'String', + 'F': 'Function', + 'O': 'Object' +}; + +function extractParamName(callee, argIndex) { + return (/.*?\((.*?)\)/).exec(callee)[1].split(', ')[argIndex]; +} + +function checkArgs(spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var type; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i), + cUpper = c.toUpperCase(), + arg = args[i]; + // Asterix means allow anything. + if (c == '*') { + continue; + } + type = Object.prototype.toString.call(arg).slice(8, -1); + if ((arg === null || arg === undefined) && c == cUpper) { + continue; + } + if (type != typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + type + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running jake test. + if (typeof jasmine == 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +moduleExports.checkArgs = checkArgs; +moduleExports.enableChecks = true; + + +}); + +// file: lib/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each(objects, func, context) { + for (var prop in objects) { + if (objects.hasOwnProperty(prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber(obj, key, value) { + obj[key] = value; + // Getters can only be overridden by getters. + if (obj[key] !== value) { + utils.defineGetter(obj, key, function() { + return value; + }); + } +} + +function assignOrWrapInDeprecateGetter(obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function() { + console.log(message); + return value; + }); + } else { + clobber(obj, key, value); + } +} + +function include(parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] == 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (merge && typeof obj.path !== 'undefined') { + // If merging, merge parent onto result + recursiveMerge(result, parent[key]); + parent[key] = result; + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch(e) { + utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge(target, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop)) { + if (typeof target.prototype !== 'undefined' && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + target.prototype[prop] = src[prop]; + } else { + if (typeof src[prop] === 'object') { + target[prop] = recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } + return target; +} + +module.exports = { + buildIntoButDoNotClobber: function(objects, target) { + include(target, objects, false, false); + }, + buildIntoAndClobber: function(objects, target) { + include(target, objects, true, false); + }, + buildIntoAndMerge: function(objects, target) { + include(target, objects, true, true); + } +}; + +}); + +// file: lib/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'), + nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onCordovaInfoReady* Internal event fired when device properties are available. + * onCordovaConnectionReady* Internal event fired when the connection property has been set. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function(type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}, + channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function(h, c) { + var len = c.length, + i = len, + f = function() { + if (!(--i)) h(); + }; + for (var j=0; j<len; j++) { + if (c[j].state === 0) { + throw Error('Can only use join with sticky channels.'); + } + c[j].subscribe(f); + } + if (!len) h(); + }, + create: function(type) { + return channel[type] = new Channel(type, false); + }, + createSticky: function(type) { + return channel[type] = new Channel(type, true); + }, + + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray: [], + deviceReadyChannelsMap: {}, + + /** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ + waitForInitialization: function(feature) { + if (feature) { + var c = channel[feature] || this.createSticky(feature); + this.deviceReadyChannelsMap[feature] = c; + this.deviceReadyChannelsArray.push(c); + } + }, + + /** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ + initializationComplete: function(feature) { + var c = this.deviceReadyChannelsMap[feature]; + if (c) { + c.fire(); + } + } + }; + +function forceFunction(f) { + if (typeof f != 'function') throw "Function required as first argument!"; +} + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Channel.prototype.subscribe = function(f, c) { + // need a function to call + forceFunction(f); + if (this.state == 2) { + f.apply(c || this, this.fireArgs); + return; + } + + var func = f, + guid = f.observer_guid; + if (typeof c == "object") { func = utils.close(c, f); } + + if (!guid) { + // first time any channel has seen this subscriber + guid = '' + nextGuid++; + } + func.observer_guid = guid; + f.observer_guid = guid; + + // Don't add the same handler more than once. + if (!this.handlers[guid]) { + this.handlers[guid] = func; + this.numHandlers++; + if (this.numHandlers == 1) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Channel.prototype.unsubscribe = function(f) { + // need a function to unsubscribe + forceFunction(f); + + var guid = f.observer_guid, + handler = this.handlers[guid]; + if (handler) { + delete this.handlers[guid]; + this.numHandlers--; + if (this.numHandlers === 0) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Calls all functions subscribed to this channel. + */ +Channel.prototype.fire = function(e) { + var fail = false, + fireArgs = Array.prototype.slice.call(arguments); + // Apply stickiness. + if (this.state == 1) { + this.state = 2; + this.fireArgs = fireArgs; + } + if (this.numHandlers) { + // Copy the values first so that it is safe to modify it from within + // callbacks. + var toCall = []; + for (var item in this.handlers) { + toCall.push(this.handlers[item]); + } + for (var i = 0; i < toCall.length; ++i) { + toCall[i].apply(this, fireArgs); + } + if (this.state == 2 && this.numHandlers) { + this.numHandlers = 0; + this.handlers = {}; + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + + +// defining them here so they are ready super fast! +// DOM event that is received when the web page is loaded and parsed. +channel.createSticky('onDOMContentLoaded'); + +// Event to indicate the Cordova native side is ready. +channel.createSticky('onNativeReady'); + +// Event to indicate that all Cordova JavaScript objects have been created +// and it's time to run plugin constructors. +channel.createSticky('onCordovaReady'); + +// Event to indicate that device properties are available +channel.createSticky('onCordovaInfoReady'); + +// Event to indicate that the connection property has been set. +channel.createSticky('onCordovaConnectionReady'); + +// Event to indicate that Cordova is ready +channel.createSticky('onDeviceReady'); + +// Event to indicate a resume lifecycle event +channel.create('onResume'); + +// Event to indicate a pause lifecycle event +channel.create('onPause'); + +// Event to indicate a destroy lifecycle event +channel.createSticky('onDestroy'); + +// Channels that must fire before "deviceready" is fired. +channel.waitForInitialization('onCordovaReady'); +channel.waitForInitialization('onCordovaConnectionReady'); + +module.exports = channel; + +}); + +// file: lib/common/commandProxy.js +define("cordova/commandProxy", function(require, exports, module) { + + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add:function(id,proxyObj) { + console.log("adding proxy for " + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove:function(id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get:function(service,action) { + return ( CommandProxyMap[service] ? CommandProxyMap[service][action] : null ); + } +}; +}); + +// file: lib/common/common.js +define("cordova/common", function(require, exports, module) { + +module.exports = { + defaults: { + cordova: { + path: 'cordova', + children: { + exec: { + path: 'cordova/exec' + }, + logger: { + path: 'cordova/plugin/logger' + } + } + }, + Cordova: { + children: { + exec: { + path: 'cordova/exec' + } + } + }, + open : { + path: 'cordova/plugin/InAppBrowser' + }, + navigator: { + children: { + notification: { + path: 'cordova/plugin/notification' + }, + accelerometer: { + path: 'cordova/plugin/accelerometer' + }, + battery: { + path: 'cordova/plugin/battery' + }, + camera:{ + path: 'cordova/plugin/Camera' + }, + compass:{ + path: 'cordova/plugin/compass' + }, + contacts: { + path: 'cordova/plugin/contacts' + }, + device:{ + children:{ + capture: { + path: 'cordova/plugin/capture' + } + } + }, + geolocation: { + path: 'cordova/plugin/geolocation' + }, + globalization: { + path: 'cordova/plugin/globalization' + }, + network: { + children: { + connection: { + path: 'cordova/plugin/network', + deprecated: 'navigator.network.connection is deprecated. Use navigator.connection instead.' + } + } + }, + splashscreen: { + path: 'cordova/plugin/splashscreen' + } + } + }, + Acceleration: { + path: 'cordova/plugin/Acceleration' + }, + Camera:{ + path: 'cordova/plugin/CameraConstants' + }, + CameraPopoverOptions: { + path: 'cordova/plugin/CameraPopoverOptions' + }, + CaptureError: { + path: 'cordova/plugin/CaptureError' + }, + CaptureAudioOptions:{ + path: 'cordova/plugin/CaptureAudioOptions' + }, + CaptureImageOptions: { + path: 'cordova/plugin/CaptureImageOptions' + }, + CaptureVideoOptions: { + path: 'cordova/plugin/CaptureVideoOptions' + }, + CompassHeading:{ + path: 'cordova/plugin/CompassHeading' + }, + CompassError:{ + path: 'cordova/plugin/CompassError' + }, + ConfigurationData: { + path: 'cordova/plugin/ConfigurationData' + }, + Connection: { + path: 'cordova/plugin/Connection' + }, + Contact: { + path: 'cordova/plugin/Contact' + }, + ContactAddress: { + path: 'cordova/plugin/ContactAddress' + }, + ContactError: { + path: 'cordova/plugin/ContactError' + }, + ContactField: { + path: 'cordova/plugin/ContactField' + }, + ContactFindOptions: { + path: 'cordova/plugin/ContactFindOptions' + }, + ContactName: { + path: 'cordova/plugin/ContactName' + }, + ContactOrganization: { + path: 'cordova/plugin/ContactOrganization' + }, + Coordinates: { + path: 'cordova/plugin/Coordinates' + }, + device: { + path: 'cordova/plugin/device' + }, + DirectoryEntry: { + path: 'cordova/plugin/DirectoryEntry' + }, + DirectoryReader: { + path: 'cordova/plugin/DirectoryReader' + }, + Entry: { + path: 'cordova/plugin/Entry' + }, + File: { + path: 'cordova/plugin/File' + }, + FileEntry: { + path: 'cordova/plugin/FileEntry' + }, + FileError: { + path: 'cordova/plugin/FileError' + }, + FileReader: { + path: 'cordova/plugin/FileReader' + }, + FileSystem: { + path: 'cordova/plugin/FileSystem' + }, + FileTransfer: { + path: 'cordova/plugin/FileTransfer' + }, + FileTransferError: { + path: 'cordova/plugin/FileTransferError' + }, + FileUploadOptions: { + path: 'cordova/plugin/FileUploadOptions' + }, + FileUploadResult: { + path: 'cordova/plugin/FileUploadResult' + }, + FileWriter: { + path: 'cordova/plugin/FileWriter' + }, + Flags: { + path: 'cordova/plugin/Flags' + }, + GlobalizationError: { + path: 'cordova/plugin/GlobalizationError' + }, + LocalFileSystem: { + path: 'cordova/plugin/LocalFileSystem' + }, + Media: { + path: 'cordova/plugin/Media' + }, + MediaError: { + path: 'cordova/plugin/MediaError' + }, + MediaFile: { + path: 'cordova/plugin/MediaFile' + }, + MediaFileData:{ + path: 'cordova/plugin/MediaFileData' + }, + Metadata:{ + path: 'cordova/plugin/Metadata' + }, + Position: { + path: 'cordova/plugin/Position' + }, + PositionError: { + path: 'cordova/plugin/PositionError' + }, + ProgressEvent: { + path: 'cordova/plugin/ProgressEvent' + }, + requestFileSystem:{ + path: 'cordova/plugin/requestFileSystem' + }, + resolveLocalFileSystemURI:{ + path: 'cordova/plugin/resolveLocalFileSystemURI' + } + }, + clobbers: { + navigator: { + children: { + connection: { + path: 'cordova/plugin/network' + } + } + } + } +}; + +}); + +// file: lib/ios/exec.js +define("cordova/exec", function(require, exports, module) { + +/** + * Creates a gap bridge iframe used to notify the native code about queued + * commands. + * + * @private + */ +var cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + jsToNativeModes = { + IFRAME_NAV: 0, + XHR_NO_PAYLOAD: 1, + XHR_WITH_PAYLOAD: 2, + XHR_OPTIONAL_PAYLOAD: 3 + }, + bridgeMode, + execIframe, + execXhr, + requestCount = 0, + vcHeaderValue = null, + commandQueue = [], // Contains pending JS->Native messages. + isInContextOfEvalJs = 0; + +function createExecIframe() { + var iframe = document.createElement("iframe"); + iframe.style.display = 'none'; + document.body.appendChild(iframe); + return iframe; +} + +function shouldBundleCommandJson() { + if (bridgeMode == jsToNativeModes.XHR_WITH_PAYLOAD) { + return true; + } + if (bridgeMode == jsToNativeModes.XHR_OPTIONAL_PAYLOAD) { + var payloadLength = 0; + for (var i = 0; i < commandQueue.length; ++i) { + payloadLength += commandQueue[i].length; + } + // The value here was determined using the benchmark within CordovaLibApp on an iPad 3. + return payloadLength < 4500; + } + return false; +} + +function iOSExec() { + // XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices. + // XHR mode's main advantage is working around a bug in -webkit-scroll, which + // doesn't exist in 4.X devices anyways. + if (bridgeMode === undefined) { + bridgeMode = navigator.userAgent.indexOf(' 4_') == -1 ? jsToNativeModes.XHR_NO_PAYLOAD : jsToNativeModes.IFRAME_NAV; + } + + var successCallback, failCallback, service, action, actionArgs, splitCommand; + var callbackId = null; + if (typeof arguments[0] !== "string") { + // FORMAT ONE + successCallback = arguments[0]; + failCallback = arguments[1]; + service = arguments[2]; + action = arguments[3]; + actionArgs = arguments[4]; + + // Since we need to maintain backwards compatibility, we have to pass + // an invalid callbackId even if no callback was provided since plugins + // will be expecting it. The Cordova.exec() implementation allocates + // an invalid callbackId and passes it even if no callbacks were given. + callbackId = 'INVALID'; + } else { + // FORMAT TWO + splitCommand = arguments[0].split("."); + action = splitCommand.pop(); + service = splitCommand.join("."); + actionArgs = Array.prototype.splice.call(arguments, 1); + } + + // Register the callbacks and add the callbackId to the positional + // arguments if given. + if (successCallback || failCallback) { + callbackId = service + cordova.callbackId++; + cordova.callbacks[callbackId] = + {success:successCallback, fail:failCallback}; + } + + var command = [callbackId, service, action, actionArgs]; + + // Stringify and queue the command. We stringify to command now to + // effectively clone the command arguments in case they are mutated before + // the command is executed. + commandQueue.push(JSON.stringify(command)); + + // If we're in the context of a stringByEvaluatingJavaScriptFromString call, + // then the queue will be flushed when it returns; no need for a poke. + // Also, if there is already a command in the queue, then we've already + // poked the native side, so there is no reason to do so again. + if (!isInContextOfEvalJs && commandQueue.length == 1) { + if (bridgeMode != jsToNativeModes.IFRAME_NAV) { + // This prevents sending an XHR when there is already one being sent. + // This should happen only in rare circumstances (refer to unit tests). + if (execXhr && execXhr.readyState != 4) { + execXhr = null; + } + // Re-using the XHR improves exec() performance by about 10%. + execXhr = execXhr || new XMLHttpRequest(); + // Changing this to a GET will make the XHR reach the URIProtocol on 4.2. + // For some reason it still doesn't work though... + execXhr.open('HEAD', "/!gap_exec", true); + if (!vcHeaderValue) { + vcHeaderValue = /.*\((.*)\)/.exec(navigator.userAgent)[1]; + } + execXhr.setRequestHeader('vc', vcHeaderValue); + execXhr.setRequestHeader('rc', ++requestCount); + if (shouldBundleCommandJson()) { + execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages()); + } + execXhr.send(null); + } else { + execIframe = execIframe || createExecIframe(); + execIframe.src = "gap://ready"; + } + } +} + +iOSExec.jsToNativeModes = jsToNativeModes; + +iOSExec.setJsToNativeBridgeMode = function(mode) { + // Remove the iFrame since it may be no longer required, and its existence + // can trigger browser bugs. + // https://issues.apache.org/jira/browse/CB-593 + if (execIframe) { + execIframe.parentNode.removeChild(execIframe); + execIframe = null; + } + bridgeMode = mode; +}; + +iOSExec.nativeFetchMessages = function() { + // Each entry in commandQueue is a JSON string already. + if (!commandQueue.length) { + return ''; + } + var json = '[' + commandQueue.join(',') + ']'; + commandQueue.length = 0; + return json; +}; + +iOSExec.nativeCallback = function(callbackId, status, payload, keepCallback) { + return iOSExec.nativeEvalAndFetch(function() { + var success = status === 0 || status === 1; + cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); + }); +}; + +iOSExec.nativeEvalAndFetch = function(func) { + // This shouldn't be nested, but better to be safe. + isInContextOfEvalJs++; + try { + func(); + return iOSExec.nativeFetchMessages(); + } finally { + isInContextOfEvalJs--; + } +}; + +module.exports = iOSExec; + +}); + +// file: lib/ios/platform.js +define("cordova/platform", function(require, exports, module) { + +module.exports = { + id: "ios", + initialize:function() { + }, + clobbers: { + File: { // exists natively, override + path: "cordova/plugin/File" + }, + FileReader: { // exists natively, override + path: "cordova/plugin/FileReader" + }, + MediaError: { // exists natively, override + path: "cordova/plugin/MediaError" + }, + console: { + path: 'cordova/plugin/ios/console' + }, + open : { + path: 'cordova/plugin/InAppBrowser' + } + }, + merges:{ + Contact:{ + path: "cordova/plugin/ios/Contact" + }, + Entry:{ + path: "cordova/plugin/ios/Entry" + }, + FileReader:{ + path: "cordova/plugin/ios/FileReader" + }, + navigator:{ + children:{ + notification:{ + path:"cordova/plugin/ios/notification" + }, + contacts:{ + path:"cordova/plugin/ios/contacts" + }, + geolocation: { + path: 'cordova/plugin/geolocation' + } + } + } + } +}; + +// use the native logger +var logger = require("cordova/plugin/logger"); +logger.useConsole(false); + +}); + +// file: lib/common/plugin/Acceleration.js +define("cordova/plugin/Acceleration", function(require, exports, module) { + +var Acceleration = function(x, y, z, timestamp) { + this.x = x; + this.y = y; + this.z = z; + this.timestamp = timestamp || (new Date()).getTime(); +}; + +module.exports = Acceleration; + +}); + +// file: lib/common/plugin/Camera.js +define("cordova/plugin/Camera", function(require, exports, module) { + +var exec = require('cordova/exec'), + Camera = require('cordova/plugin/CameraConstants'); + +var cameraExport = {}; + +// Tack on the Camera Constants to the base camera plugin. +for (var key in Camera) { + cameraExport[key] = Camera[key]; +} + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=FILE_URI. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +cameraExport.getPicture = function(successCallback, errorCallback, options) { + options = options || {}; + // successCallback required + if (typeof successCallback != "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + var quality = 50; + if (typeof options.quality == "number") { + quality = options.quality; + } else if (typeof options.quality == "string") { + var qlity = parseInt(options.quality, 10); + if (isNaN(qlity) === false) { + quality = qlity.valueOf(); + } + } + + var destinationType = Camera.DestinationType.FILE_URI; + if (typeof options.destinationType == "number") { + destinationType = options.destinationType; + } + + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof options.sourceType == "number") { + sourceType = options.sourceType; + } + + var targetWidth = -1; + if (typeof options.targetWidth == "number") { + targetWidth = options.targetWidth; + } else if (typeof options.targetWidth == "string") { + var width = parseInt(options.targetWidth, 10); + if (isNaN(width) === false) { + targetWidth = width.valueOf(); + } + } + + var targetHeight = -1; + if (typeof options.targetHeight == "number") { + targetHeight = options.targetHeight; + } else if (typeof options.targetHeight == "string") { + var height = parseInt(options.targetHeight, 10); + if (isNaN(height) === false) { + targetHeight = height.valueOf(); + } + } + + var encodingType = Camera.EncodingType.JPEG; + if (typeof options.encodingType == "number") { + encodingType = options.encodingType; + } + + var mediaType = Camera.MediaType.PICTURE; + if (typeof options.mediaType == "number") { + mediaType = options.mediaType; + } + var allowEdit = false; + if (typeof options.allowEdit == "boolean") { + allowEdit = options.allowEdit; + } else if (typeof options.allowEdit == "number") { + allowEdit = options.allowEdit <= 0 ? false : true; + } + var correctOrientation = false; + if (typeof options.correctOrientation == "boolean") { + correctOrientation = options.correctOrientation; + } else if (typeof options.correctOrientation == "number") { + correctOrientation = options.correctOrientation <=0 ? false : true; + } + var saveToPhotoAlbum = false; + if (typeof options.saveToPhotoAlbum == "boolean") { + saveToPhotoAlbum = options.saveToPhotoAlbum; + } else if (typeof options.saveToPhotoAlbum == "number") { + saveToPhotoAlbum = options.saveToPhotoAlbum <=0 ? false : true; + } + var popoverOptions = null; + if (typeof options.popoverOptions == "object") { + popoverOptions = options.popoverOptions; + } + + var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions]; + + exec(successCallback, errorCallback, "Camera", "takePicture", args); +}; + +cameraExport.cleanup = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "Camera", "cleanup", []); +}; + +module.exports = cameraExport; + +}); + +// file: lib/common/plugin/CameraConstants.js +define("cordova/plugin/CameraConstants", function(require, exports, module) { + +module.exports = { + DestinationType:{ + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) + }, + EncodingType:{ + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image + }, + MediaType:{ + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types + }, + PictureSourceType:{ + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) + }, + PopoverArrowDirection:{ + ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants to specify arrow location on popover + ARROW_DOWN : 2, + ARROW_LEFT : 4, + ARROW_RIGHT : 8, + ARROW_ANY : 15 + } +}; + +}); + +// file: lib/common/plugin/CameraPopoverOptions.js +define("cordova/plugin/CameraPopoverOptions", function(require, exports, module) { + +var Camera = require('cordova/plugin/CameraConstants'); + +/** + * Encapsulates options for iOS Popover image picker + */ +var CameraPopoverOptions = function(x,y,width,height,arrowDir){ + // information of rectangle that popover should be anchored to + this.x = x || 0; + this.y = y || 32; + this.width = width || 320; + this.height = height || 480; + // The direction of the popover arrow + this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY; +}; + +module.exports = CameraPopoverOptions; + +}); + +// file: lib/common/plugin/CaptureAudioOptions.js +define("cordova/plugin/CaptureAudioOptions", function(require, exports, module) { + +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; + // The selected audio mode. Must match with one of the elements in supportedAudioModes array. + this.mode = null; +}; + +module.exports = CaptureAudioOptions; + +}); + +// file: lib/common/plugin/CaptureError.js +define("cordova/plugin/CaptureError", function(require, exports, module) { + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(c) { + this.code = c || null; +}; + +// Camera or microphone failed to capture image or sound. +CaptureError.CAPTURE_INTERNAL_ERR = 0; +// Camera application or audio capture application is currently serving other capture request. +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +// Invalid use of the API (e.g. limit parameter has value less than one). +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +// User exited camera application or audio capture application before capturing anything. +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +// The requested capture operation is not supported. +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +module.exports = CaptureError; + +}); + +// file: lib/common/plugin/CaptureImageOptions.js +define("cordova/plugin/CaptureImageOptions", function(require, exports, module) { + +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; + // The selected image mode. Must match with one of the elements in supportedImageModes array. + this.mode = null; +}; + +module.exports = CaptureImageOptions; + +}); + +// file: lib/common/plugin/CaptureVideoOptions.js +define("cordova/plugin/CaptureVideoOptions", function(require, exports, module) { + +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; + // The selected video mode. Must match with one of the elements in supportedVideoModes array. + this.mode = null; +}; + +module.exports = CaptureVideoOptions; + +}); + +// file: lib/common/plugin/CompassError.js +define("cordova/plugin/CompassError", function(require, exports, module) { + +/** + * CompassError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var CompassError = function(err) { + this.code = (err !== undefined ? err : null); +}; + +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +module.exports = CompassError; + +}); + +// file: lib/common/plugin/CompassHeading.js +define("cordova/plugin/CompassHeading", function(require, exports, module) { + +var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { + this.magneticHeading = (magneticHeading !== undefined ? magneticHeading : null); + this.trueHeading = (trueHeading !== undefined ? trueHeading : null); + this.headingAccuracy = (headingAccuracy !== undefined ? headingAccuracy : null); + this.timestamp = (timestamp !== undefined ? timestamp : new Date().getTime()); +}; + +module.exports = CompassHeading; + +}); + +// file: lib/common/plugin/ConfigurationData.js +define("cordova/plugin/ConfigurationData", function(require, exports, module) { + +/** + * Encapsulates a set of parameters that the capture device supports. + */ +function ConfigurationData() { + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +} + +module.exports = ConfigurationData; + +}); + +// file: lib/common/plugin/Connection.js +define("cordova/plugin/Connection", function(require, exports, module) { + +/** + * Network status + */ +module.exports = { + UNKNOWN: "unknown", + ETHERNET: "ethernet", + WIFI: "wifi", + CELL_2G: "2g", + CELL_3G: "3g", + CELL_4G: "4g", + NONE: "none" +}; + +}); + +// file: lib/common/plugin/Contact.js +define("cordova/plugin/Contact", function(require, exports, module) { + +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'); + +/** +* Converts primitives into Complex Object +* Currently only used for Date fields +*/ +function convertIn(contact) { + var value = contact.birthday; + try { + contact.birthday = new Date(parseFloat(value)); + } catch (exception){ + console.log("Cordova Contact convertIn error: exception creating date."); + } + return contact; +} + +/** +* Converts Complex objects into primitives +* Only conversion at present is for Dates. +**/ + +function convertOut(contact) { + var value = contact.birthday; + if (value !== null) { + // try to make it a Date object if it is not already + if (!utils.isDate(value)){ + try { + value = new Date(value); + } catch(exception){ + value = null; + } + } + if (utils.isDate(value)){ + value = value.valueOf(); // convert to milliseconds + } + contact.birthday = value; + } + return contact; +} + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.<ContactField>} phoneNumbers array of phone numbers +* @param {Array.<ContactField>} emails array of email addresses +* @param {Array.<ContactAddress>} addresses array of addresses +* @param {Array.<ContactField>} ims instant messaging user ids +* @param {Array.<ContactOrganization>} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.<ContactField>} photos +* @param {Array.<ContactField>} categories +* @param {Array.<ContactField>} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + var fail = function(code) { + errorCB(new ContactError(code)); + }; + if (this.id === null) { + fail(ContactError.UNKNOWN_ERROR); + } + else { + exec(successCB, fail, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = utils.clone(this); + var i; + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i = 0; i < clonedContact.phoneNumbers.length; i++) { + clonedContact.phoneNumbers[i].id = null; + } + } + if (clonedContact.emails) { + for (i = 0; i < clonedContact.emails.length; i++) { + clonedContact.emails[i].id = null; + } + } + if (clonedContact.addresses) { + for (i = 0; i < clonedContact.addresses.length; i++) { + clonedContact.addresses[i].id = null; + } + } + if (clonedContact.ims) { + for (i = 0; i < clonedContact.ims.length; i++) { + clonedContact.ims[i].id = null; + } + } + if (clonedContact.organizations) { + for (i = 0; i < clonedContact.organizations.length; i++) { + clonedContact.organizations[i].id = null; + } + } + if (clonedContact.categories) { + for (i = 0; i < clonedContact.categories.length; i++) { + clonedContact.categories[i].id = null; + } + } + if (clonedContact.photos) { + for (i = 0; i < clonedContact.photos.length; i++) { + clonedContact.photos[i].id = null; + } + } + if (clonedContact.urls) { + for (i = 0; i < clonedContact.urls.length; i++) { + clonedContact.urls[i].id = null; + } + } + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + var fail = function(code) { + errorCB(new ContactError(code)); + }; + var success = function(result) { + if (result) { + if (typeof successCB === 'function') { + var fullContact = require('cordova/plugin/contacts').create(result); + successCB(convertIn(fullContact)); + } + } + else { + // no Entry object returned + fail(ContactError.UNKNOWN_ERROR); + } + }; + var dupContact = convertOut(utils.clone(this)); + exec(success, fail, "Contacts", "save", [dupContact]); +}; + + +module.exports = Contact; + +}); + +// file: lib/common/plugin/ContactAddress.js +define("cordova/plugin/ContactAddress", function(require, exports, module) { + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted // NOTE: not a W3C standard +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ + +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +module.exports = ContactAddress; + +}); + +// file: lib/common/plugin/ContactError.js +define("cordova/plugin/ContactError", function(require, exports, module) { + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var ContactError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +module.exports = ContactError; + +}); + +// file: lib/common/plugin/ContactField.js +define("cordova/plugin/ContactField", function(require, exports, module) { + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = (type && type.toString()) || null; + this.value = (value && value.toString()) || null; + this.pref = (typeof pref != 'undefined' ? pref : false); +}; + +module.exports = ContactField; + +}); + +// file: lib/common/plugin/ContactFindOptions.js +define("cordova/plugin/ContactFindOptions", function(require, exports, module) { + +/** + * ContactFindOptions. + * @constructor + * @param filter used to match contacts against + * @param multiple boolean used to determine if more than one contact should be returned + */ + +var ContactFindOptions = function(filter, multiple) { + this.filter = filter || ''; + this.multiple = (typeof multiple != 'undefined' ? multiple : false); +}; + +module.exports = ContactFindOptions; + +}); + +// file: lib/common/plugin/ContactName.js +define("cordova/plugin/ContactName", function(require, exports, module) { + +/** +* Contact name. +* @constructor +* @param formatted // NOTE: not part of W3C standard +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +module.exports = ContactName; + +}); + +// file: lib/common/plugin/ContactOrganization.js +define("cordova/plugin/ContactOrganization", function(require, exports, module) { + +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ + +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +module.exports = ContactOrganization; + +}); + +// file: lib/common/plugin/Coordinates.js +define("cordova/plugin/Coordinates", function(require, exports, module) { + +/** + * This class contains position information. + * @param {Object} lat + * @param {Object} lng + * @param {Object} alt + * @param {Object} acc + * @param {Object} head + * @param {Object} vel + * @param {Object} altacc + * @constructor + */ +var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { + /** + * The latitude of the position. + */ + this.latitude = lat; + /** + * The longitude of the position, + */ + this.longitude = lng; + /** + * The accuracy of the position. + */ + this.accuracy = acc; + /** + * The altitude of the position. + */ + this.altitude = (alt !== undefined ? alt : null); + /** + * The direction the device is moving at the position. + */ + this.heading = (head !== undefined ? head : null); + /** + * The velocity with which the device is moving at the position. + */ + this.speed = (vel !== undefined ? vel : null); + + if (this.speed === 0 || this.speed === null) { + this.heading = NaN; + } + + /** + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; +}; + +module.exports = Coordinates; + +}); + +// file: lib/common/plugin/DirectoryEntry.js +define("cordova/plugin/DirectoryEntry", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileError = require('cordova/plugin/FileError'), + DirectoryReader = require('cordova/plugin/DirectoryReader'); + +/** + * An interface representing a directory on the file system. + * + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * TODO: implement this!!! {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function(name, fullPath) { + DirectoryEntry.__super__.constructor.apply(this, [false, true, name, fullPath]); +}; + +utils.extend(DirectoryEntry, Entry); + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function() { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or exclusively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or exclusively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var FileEntry = require('cordova/plugin/FileEntry'); + var entry = new FileEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFile", [this.fullPath, path, options]); +}; + +module.exports = DirectoryEntry; + +}); + +// file: lib/common/plugin/DirectoryReader.js +define("cordova/plugin/DirectoryReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError') ; + +/** + * An interface that lists the files and directories in a directory. + */ +function DirectoryReader(path) { + this.path = path || null; +} + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i<result.length; i++) { + var entry = null; + if (result[i].isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result[i].isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result[i].isDirectory; + entry.isFile = result[i].isFile; + entry.name = result[i].name; + entry.fullPath = result[i].fullPath; + retVal.push(entry); + } + successCallback(retVal); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "readEntries", [this.path]); +}; + +module.exports = DirectoryReader; + +}); + +// file: lib/common/plugin/Entry.js +define("cordova/plugin/Entry", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + Metadata = require('cordova/plugin/Metadata'); + +/** + * Represents a file or directory on the local file system. + * + * @param isFile + * {boolean} true if Entry is a file (readonly) + * @param isDirectory + * {boolean} true if Entry is a directory (readonly) + * @param name + * {DOMString} name of the file or directory, excluding the path + * leading to it (readonly) + * @param fullPath + * {DOMString} the absolute full path to the file or directory + * (readonly) + */ +function Entry(isFile, isDirectory, name, fullPath, fileSystem) { + this.isFile = (typeof isFile != 'undefined'?isFile:false); + this.isDirectory = (typeof isDirectory != 'undefined'?isDirectory:false); + this.name = name || ''; + this.fullPath = fullPath || ''; + this.filesystem = fileSystem || null; +} + +/** + * Look up the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + */ +Entry.prototype.getMetadata = function(successCallback, errorCallback) { + var success = typeof successCallback !== 'function' ? null : function(lastModified) { + var metadata = new Metadata(lastModified); + successCallback(metadata); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + + exec(success, fail, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Set the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + * @param metadataObject + * {Object} keys and values to set + */ +Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) { + + exec(successCallback, errorCallback, "File", "setMetadata", [this.fullPath, metadataObject]); +}; + +/** + * Move a file or directory to a new location. + * + * @param parent + * {DirectoryEntry} the directory to which to move this entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new DirectoryEntry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + var fail = function(code) { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(code)); + } + }; + // user must specify parent Entry + if (!parent) { + fail(FileError.NOT_FOUND_ERR); + return; + } + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + success = function(entry) { + if (entry) { + if (typeof successCallback === 'function') { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + try { + successCallback(result); + } + catch (e) { + console.log('Error invoking callback: ' + e); + } + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "moveTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Copy a directory to a different location. + * + * @param parent + * {DirectoryEntry} the directory to which to copy the entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new Entry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + var fail = function(code) { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(code)); + } + }; + + // user must specify parent Entry + if (!parent) { + fail(FileError.NOT_FOUND_ERR); + return; + } + + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + // success callback + success = function(entry) { + if (entry) { + if (typeof successCallback === 'function') { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + try { + successCallback(result); + } + catch (e) { + console.log('Error invoking callback: ' + e); + } + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "copyTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Return a URL that can be used to identify this entry. + */ +Entry.prototype.toURL = function() { + // fullPath attribute contains the full URL + return this.fullPath; +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +Entry.prototype.toURI = function(mimeType) { + console.log("DEPRECATED: Update your code to use 'toURL'"); + // fullPath attribute contains the full URI + return this.toURL(); +}; + +/** + * Remove a file or directory. It is an error to attempt to delete a + * directory that is not empty. It is an error to attempt to delete a + * root directory of a file system. + * + * @param successCallback {Function} called with no parameters + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.remove = function(successCallback, errorCallback) { + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "remove", [this.fullPath]); +}; + +/** + * Look up the parent DirectoryEntry of this entry. + * + * @param successCallback {Function} called with the parent DirectoryEntry object + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.getParent = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getParent", [this.fullPath]); +}; + +module.exports = Entry; + +}); + +// file: lib/common/plugin/File.js +define("cordova/plugin/File", function(require, exports, module) { + +/** + * Constructor. + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ + +var File = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || ''; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; +}; + +module.exports = File; + +}); + +// file: lib/common/plugin/FileEntry.js +define("cordova/plugin/FileEntry", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileWriter = require('cordova/plugin/FileWriter'), + File = require('cordova/plugin/File'), + FileError = require('cordova/plugin/FileError'); + +/** + * An interface representing a file on the file system. + * + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the file resides (readonly) + */ +var FileEntry = function(name, fullPath) { + FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath]); +}; + +utils.extend(FileEntry, Entry); + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + if (typeof errorCallback === "function") { + errorCallback(new FileError(FileError.INVALID_STATE_ERR)); + } + } else { + if (typeof successCallback === "function") { + successCallback(writer); + } + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(f) { + var file = new File(f.name, f.fullPath, f.type, f.lastModifiedDate, f.size); + successCallback(file); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFileMetadata", [this.fullPath]); +}; + + +module.exports = FileEntry; + +}); + +// file: lib/common/plugin/FileError.js +define("cordova/plugin/FileError", function(require, exports, module) { + +/** + * FileError + */ +function FileError(error) { + this.code = error || null; +} + +// File error codes +// Found in DOMException +FileError.NOT_FOUND_ERR = 1; +FileError.SECURITY_ERR = 2; +FileError.ABORT_ERR = 3; + +// Added by File API specification +FileError.NOT_READABLE_ERR = 4; +FileError.ENCODING_ERR = 5; +FileError.NO_MODIFICATION_ALLOWED_ERR = 6; +FileError.INVALID_STATE_ERR = 7; +FileError.SYNTAX_ERR = 8; +FileError.INVALID_MODIFICATION_ERR = 9; +FileError.QUOTA_EXCEEDED_ERR = 10; +FileError.TYPE_MISMATCH_ERR = 11; +FileError.PATH_EXISTS_ERR = 12; + +module.exports = FileError; + +}); + +// file: lib/common/plugin/FileReader.js +define("cordova/plugin/FileReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + * @constructor + */ +var FileReader = function() { + this.fileName = ""; + + this.readyState = 0; // FileReader.EMPTY + + // File data + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onloadstart = null; // When the read starts. + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) + this.onload = null; // When the read has successfully completed. + this.onerror = null; // When the read has failed (see errors). + this.onloadend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the read has been aborted. For instance, by invoking the abort() method. +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + this.result = null; + + if (this.readyState == FileReader.DONE || this.readyState == FileReader.EMPTY) { + return; + } + + this.readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort === 'function') { + this.onabort(new ProgressEvent('abort', {target:this})); + } + // If load end callback + if (typeof this.onloadend === 'function') { + this.onloadend(new ProgressEvent('loadend', {target:this})); + } +}; + +/** + * Read text file. + * + * @param file {File} File object containing file properties + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + // Figure out pathing + this.fileName = ''; + if (typeof file.fullPath === 'undefined') { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // Already loading something + if (this.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart(new ProgressEvent("loadstart", {target:this})); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + + var me = this; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + // null result + me.result = null; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsText", [this.fileName, enc]); +}; + + +/** + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[<mediatype>][;base64],<data> + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // Already loading something + if (this.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart(new ProgressEvent("loadstart", {target:this})); + } + + var me = this; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + me.result = null; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsDataURL", [this.fileName]); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + console.log('method "readAsBinaryString" is not supported at this time.'); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + console.log('This method is not supported at this time.'); +}; + +module.exports = FileReader; + +}); + +// file: lib/common/plugin/FileSystem.js +define("cordova/plugin/FileSystem", function(require, exports, module) { + +var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function(name, root) { + this.name = name || null; + if (root) { + this.root = new DirectoryEntry(root.name, root.fullPath); + } +}; + +module.exports = FileSystem; + +}); + +// file: lib/common/plugin/FileTransfer.js +define("cordova/plugin/FileTransfer", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileTransferError = require('cordova/plugin/FileTransferError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +function newProgressEvent(result) { + var pe = new ProgressEvent(); + pe.lengthComputable = result.lengthComputable; + pe.loaded = result.loaded; + pe.total = result.total; + return pe; +} + +var idCounter = 0; + +/** + * FileTransfer uploads a file to a remote server. + * @constructor + */ +var FileTransfer = function() { + this._id = ++idCounter; + this.onprogress = null; // optional callback +}; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { + // sanity parameter checking + if (!filePath || !server) throw new Error("FileTransfer.upload requires filePath and server URL parameters at the minimum."); + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + var chunkedMode = true; + var headers = null; + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + headers = options.headers; + if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { + chunkedMode = options.chunkedMode; + } + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + var fail = function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + errorCallback(error); + }; + + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + return successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); +}; + +/** + * Downloads a file form a given URL and saves it to the specified directory. + * @param source {String} URL of the server to receive the file + * @param target {String} Full path of the file on the device + * @param successCallback (Function} Callback to be invoked when upload has completed + * @param errorCallback {Function} Callback to be invoked upon error + * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + */ +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { + // sanity parameter checking + if (!source || !target) throw new Error("FileTransfer.download requires source URI and target URI parameters at the minimum."); + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + var entry = null; + if (result.isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result.isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + successCallback(entry); + } + }; + + var fail = function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + errorCallback(error); + }; + + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id]); +}; + +/** + * Aborts the ongoing file transfer on this object + * @param successCallback {Function} Callback to be invoked upon success + * @param errorCallback {Function} Callback to be invoked upon error + */ +FileTransfer.prototype.abort = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, 'FileTransfer', 'abort', [this._id]); +}; + +module.exports = FileTransfer; + +}); + +// file: lib/common/plugin/FileTransferError.js +define("cordova/plugin/FileTransferError", function(require, exports, module) { + +/** + * FileTransferError + * @constructor + */ +var FileTransferError = function(code, source, target, status) { + this.code = code || null; + this.source = source || null; + this.target = target || null; + this.http_status = status || null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; + +module.exports = FileTransferError; + +}); + +// file: lib/common/plugin/FileUploadOptions.js +define("cordova/plugin/FileUploadOptions", function(require, exports, module) { + +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + * @param headers {Object} Keys are header names, values are header values. Multiple + * headers of the same name are not supported. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; + this.headers = headers || null; +}; + +module.exports = FileUploadOptions; + +}); + +// file: lib/common/plugin/FileUploadResult.js +define("cordova/plugin/FileUploadResult", function(require, exports, module) { + +/** + * FileUploadResult + * @constructor + */ +var FileUploadResult = function() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +module.exports = FileUploadResult; + +}); + +// file: lib/common/plugin/FileWriter.js +define("cordova/plugin/FileWriter", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // set error + this.error = new FileError(FileError.ABORT_ERR); + + this.readyState = FileWriter.DONE; + + // If abort callback + if (typeof this.onabort === "function") { + this.onabort(new ProgressEvent("abort", {"target":this})); + } + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend(new ProgressEvent("writeend", {"target":this})); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":me})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + + me.length = me.position; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "write", [this.fileName, text, this.position]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + if (!offset && offset !== 0) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger than file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":this})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "truncate", [this.fileName, size]); +}; + +module.exports = FileWriter; + +}); + +// file: lib/common/plugin/Flags.js +define("cordova/plugin/Flags", function(require, exports, module) { + +/** + * Supplies arguments to methods that lookup or create files and directories. + * + * @param create + * {boolean} file or directory if it doesn't exist + * @param exclusive + * {boolean} used with create; if true the command will fail if + * target path exists + */ +function Flags(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +} + +module.exports = Flags; + +}); + +// file: lib/common/plugin/GlobalizationError.js +define("cordova/plugin/GlobalizationError", function(require, exports, module) { + + +/** + * Globalization error object + * + * @constructor + * @param code + * @param message + */ +var GlobalizationError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +// Globalization error codes +GlobalizationError.UNKNOWN_ERROR = 0; +GlobalizationError.FORMATTING_ERROR = 1; +GlobalizationError.PARSING_ERROR = 2; +GlobalizationError.PATTERN_ERROR = 3; + +module.exports = GlobalizationError; + +}); + +// file: lib/common/plugin/InAppBrowser.js +define("cordova/plugin/InAppBrowser", function(require, exports, module) { + +var exec = require('cordova/exec'); + +function InAppBrowser() +{ + var _channel = require('cordova/channel'); + this.channels = { + 'loadstart': _channel.create('loadstart'), + 'loadstop' : _channel.create('loadstop'), + 'exit' : _channel.create('exit') + }; +} + +InAppBrowser.prototype._eventHandler = function(event) +{ + if (event.type in this.channels) { + this.channels[event.type].fire(event); + } +} + +InAppBrowser.open = function(strUrl, strWindowName, strWindowFeatures) +{ + var iab = new InAppBrowser(); + var cb = function(eventname) { + iab._eventHandler(eventname); + } + exec(cb, null, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); + return iab; +} + +InAppBrowser.prototype.close = function(eventname, f) +{ + exec(null, null, "InAppBrowser", "close", []); +} + +InAppBrowser.prototype.addEventListener = function(eventname, f) +{ + if (eventname in this.channels) { + this.channels[eventname].subscribe(f); + } +} + +InAppBrowser.prototype.removeEventListener = function(eventname, f) +{ + if (eventname in this.channels) { + this.channels[eventname].unsubscribe(f); + } +} + +module.exports = InAppBrowser.open; + + +}); + +// file: lib/common/plugin/LocalFileSystem.js +define("cordova/plugin/LocalFileSystem", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Represents a local file system. + */ +var LocalFileSystem = function() { + +}; + +LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence +LocalFileSystem.PERSISTENT = 1; //persistent + +module.exports = LocalFileSystem; + +}); + +// file: lib/common/plugin/Media.js +define("cordova/plugin/Media", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var mediaObjects = {}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @constructor + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + */ +var Media = function(src, successCallback, errorCallback, statusCallback) { + + // successCallback optional + if (successCallback && (typeof successCallback !== "function")) { + console.log("Media Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Media Error: errorCallback is not a function"); + return; + } + + // statusCallback optional + if (statusCallback && (typeof statusCallback !== "function")) { + console.log("Media Error: statusCallback is not a function"); + return; + } + + this.id = utils.createUUID(); + mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; + this._position = -1; + exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_POSITION = 3; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// "static" function to return existing objs. +Media.get = function(id) { + return mediaObjects[id]; +}; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function(options) { + exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + var me = this; + exec(function() { + me._position = 0; + }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Seek or jump to a new time in the track.. + */ +Media.prototype.seekTo = function(milliseconds) { + var me = this; + exec(function(p) { + me._position = p; + }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + */ +Media.prototype.getCurrentPosition = function(success, fail) { + var me = this; + exec(function(p) { + me._position = p; + success(p); + }, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + exec(null, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + exec(null, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + exec(null, this.errorCallback, "Media", "release", [this.id]); +}; + +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param msgType The 'type' of update this is + * @param value Use of value is determined by the msgType + */ +Media.onStatus = function(id, msgType, value) { + + var media = mediaObjects[id]; + + if(media) { + switch(msgType) { + case Media.MEDIA_STATE : + media.statusCallback && media.statusCallback(value); + if(value == Media.MEDIA_STOPPED) { + media.successCallback && media.successCallback(); + } + break; + case Media.MEDIA_DURATION : + media._duration = value; + break; + case Media.MEDIA_ERROR : + media.errorCallback && media.errorCallback(value); + break; + case Media.MEDIA_POSITION : + media._position = Number(value); + break; + default : + console && console.error && console.error("Unhandled Media.onStatus :: " + msgType); + break; + } + } + else { + console && console.error && console.error("Received Media.onStatus callback for unknown media :: " + id); + } + +}; + +module.exports = Media; + +}); + +// file: lib/common/plugin/MediaError.js +define("cordova/plugin/MediaError", function(require, exports, module) { + +/** + * This class contains information about any Media errors. +*/ +/* + According to :: http://dev.w3.org/html5/spec-author-view/video.html#mediaerror + We should never be creating these objects, we should just implement the interface + which has 1 property for an instance, 'code' + + instead of doing : + errorCallbackFunction( new MediaError(3,'msg') ); +we should simply use a literal : + errorCallbackFunction( {'code':3} ); + */ + + var _MediaError = window.MediaError; + + +if(!_MediaError) { + window.MediaError = _MediaError = function(code, msg) { + this.code = (typeof code != 'undefined') ? code : null; + this.message = msg || ""; // message is NON-standard! do not use! + }; +} + +_MediaError.MEDIA_ERR_NONE_ACTIVE = _MediaError.MEDIA_ERR_NONE_ACTIVE || 0; +_MediaError.MEDIA_ERR_ABORTED = _MediaError.MEDIA_ERR_ABORTED || 1; +_MediaError.MEDIA_ERR_NETWORK = _MediaError.MEDIA_ERR_NETWORK || 2; +_MediaError.MEDIA_ERR_DECODE = _MediaError.MEDIA_ERR_DECODE || 3; +_MediaError.MEDIA_ERR_NONE_SUPPORTED = _MediaError.MEDIA_ERR_NONE_SUPPORTED || 4; +// TODO: MediaError.MEDIA_ERR_NONE_SUPPORTED is legacy, the W3 spec now defines it as below. +// as defined by http://dev.w3.org/html5/spec-author-view/video.html#error-codes +_MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = _MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED || 4; + +module.exports = _MediaError; + +}); + +// file: lib/common/plugin/MediaFile.js +define("cordova/plugin/MediaFile", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + File = require('cordova/plugin/File'), + CaptureError = require('cordova/plugin/CaptureError'); +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + MediaFile.__super__.constructor.apply(this, arguments); +}; + +utils.extend(MediaFile, File); + +/** + * Request capture format data for a specific file and type + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { + if (typeof this.fullPath === "undefined" || this.fullPath === null) { + errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); + } else { + exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); + } +}; + +module.exports = MediaFile; + +}); + +// file: lib/common/plugin/MediaFileData.js +define("cordova/plugin/MediaFileData", function(require, exports, module) { + +/** + * MediaFileData encapsulates format information of a media file. + * + * @param {DOMString} codecs + * @param {long} bitrate + * @param {long} height + * @param {long} width + * @param {float} duration + */ +var MediaFileData = function(codecs, bitrate, height, width, duration){ + this.codecs = codecs || null; + this.bitrate = bitrate || 0; + this.height = height || 0; + this.width = width || 0; + this.duration = duration || 0; +}; + +module.exports = MediaFileData; + +}); + +// file: lib/common/plugin/Metadata.js +define("cordova/plugin/Metadata", function(require, exports, module) { + +/** + * Information about the state of the file or directory + * + * {Date} modificationTime (readonly) + */ +var Metadata = function(time) { + this.modificationTime = (typeof time != 'undefined'?new Date(time):null); +}; + +module.exports = Metadata; + +}); + +// file: lib/common/plugin/Position.js +define("cordova/plugin/Position", function(require, exports, module) { + +var Coordinates = require('cordova/plugin/Coordinates'); + +var Position = function(coords, timestamp) { + if (coords) { + this.coords = new Coordinates(coords.latitude, coords.longitude, coords.altitude, coords.accuracy, coords.heading, coords.velocity, coords.altitudeAccuracy); + } else { + this.coords = new Coordinates(); + } + this.timestamp = (timestamp !== undefined) ? timestamp : new Date(); +}; + +module.exports = Position; + +}); + +// file: lib/common/plugin/PositionError.js +define("cordova/plugin/PositionError", function(require, exports, module) { + +/** + * Position error object + * + * @constructor + * @param code + * @param message + */ +var PositionError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +module.exports = PositionError; + +}); + +// file: lib/common/plugin/ProgressEvent.js +define("cordova/plugin/ProgressEvent", function(require, exports, module) { + +// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill +// Feature test: See if we can instantiate a native ProgressEvent; +// if so, use that approach, +// otherwise fill-in with our own implementation. +// +// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview. +var ProgressEvent = (function() { + /* + var createEvent = function(data) { + var event = document.createEvent('Events'); + event.initEvent('ProgressEvent', false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + if (data.target) { + // TODO: cannot call <some_custom_object>.dispatchEvent + // need to first figure out how to implement EventTarget + } + } + return event; + }; + try { + var ev = createEvent({type:"abort",target:document}); + return function ProgressEvent(type, data) { + data.type = type; + return createEvent(data); + }; + } catch(e){ + */ + return function ProgressEvent(type, dict) { + this.type = type; + this.bubbles = false; + this.cancelBubble = false; + this.cancelable = false; + this.lengthComputable = false; + this.loaded = dict && dict.loaded ? dict.loaded : 0; + this.total = dict && dict.total ? dict.total : 0; + this.target = dict && dict.target ? dict.target : null; + }; + //} +})(); + +module.exports = ProgressEvent; + +}); + +// file: lib/common/plugin/accelerometer.js +define("cordova/plugin/accelerometer", function(require, exports, module) { + +/** + * This class provides access to device accelerometer data. + * @constructor + */ +var argscheck = require('cordova/argscheck'), + utils = require("cordova/utils"), + exec = require("cordova/exec"), + Acceleration = require('cordova/plugin/Acceleration'); + +// Is the accel sensor running? +var running = false; + +// Keeps reference to watchAcceleration calls. +var timers = {}; + +// Array of listeners; used to keep track of when we should call start and stop. +var listeners = []; + +// Last returned acceleration object from native +var accel = null; + +// Tells native to start. +function start() { + exec(function(a) { + var tempListeners = listeners.slice(0); + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].win(accel); + } + }, function(e) { + var tempListeners = listeners.slice(0); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].fail(e); + } + }, "Accelerometer", "start", []); + running = true; +} + +// Tells native to stop. +function stop() { + exec(null, null, "Accelerometer", "stop", []); + running = false; +} + +// Adds a callback pair to the listeners array +function createCallbackPair(win, fail) { + return {win:win, fail:fail}; +} + +// Removes a win/fail listener pair from the listeners array +function removeListeners(l) { + var idx = listeners.indexOf(l); + if (idx > -1) { + listeners.splice(idx, 1); + if (listeners.length === 0) { + stop(); + } + } +} + +var accelerometer = { + /** + * Asynchronously acquires the current acceleration. + * + * @param {Function} successCallback The function to call when the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + */ + getCurrentAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.getCurrentAcceleration', arguments); + + var p; + var win = function(a) { + removeListeners(p); + successCallback(a); + }; + var fail = function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }; + + p = createCallbackPair(win, fail); + listeners.push(p); + + if (!running) { + start(); + } + }, + + /** + * Asynchronously acquires the acceleration repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.watchAcceleration', arguments); + // Default interval (10 sec) + var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000; + + // Keep reference to watch id, and report accel readings as often as defined in frequency + var id = utils.createUUID(); + + var p = createCallbackPair(function(){}, function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }); + listeners.push(p); + + timers[id] = { + timer:window.setInterval(function() { + if (accel) { + successCallback(accel); + } + }, frequency), + listeners:p + }; + + if (running) { + // If we're already running then immediately invoke the success callback + // but only if we have retrieved a value, sample code does not check for null ... + if (accel) { + successCallback(accel); + } + } else { + start(); + } + + return id; + }, + + /** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ + clearWatch: function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + window.clearInterval(timers[id].timer); + removeListeners(timers[id].listeners); + delete timers[id]; + } + } +}; + +module.exports = accelerometer; + +}); + +// file: lib/common/plugin/battery.js +define("cordova/plugin/battery", function(require, exports, module) { + +/** + * This class contains information about the current battery status. + * @constructor + */ +var cordova = require('cordova'), + exec = require('cordova/exec'); + +function handlers() { + return battery.channels.batterystatus.numHandlers + + battery.channels.batterylow.numHandlers + + battery.channels.batterycritical.numHandlers; +} + +var Battery = function() { + this._level = null; + this._isPlugged = null; + // Create new event handlers on the window (returns a channel instance) + this.channels = { + batterystatus:cordova.addWindowEventHandler("batterystatus"), + batterylow:cordova.addWindowEventHandler("batterylow"), + batterycritical:cordova.addWindowEventHandler("batterycritical") + }; + for (var key in this.channels) { + this.channels[key].onHasSubscribersChange = Battery.onHasSubscribersChange; + } +}; +/** + * Event handlers for when callbacks get registered for the battery. + * Keep track of how many handlers we have so we can start and stop the native battery listener + * appropriately (and hopefully save on battery life!). + */ +Battery.onHasSubscribersChange = function() { + // If we just registered the first handler, make sure native listener is started. + if (this.numHandlers === 1 && handlers() === 1) { + exec(battery._status, battery._error, "Battery", "start", []); + } else if (handlers() === 0) { + exec(null, null, "Battery", "stop", []); + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = battery; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + cordova.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + cordova.fireWindowEvent("batterylow", info); + } + else { + cordova.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +var battery = new Battery(); + +module.exports = battery; + +}); + +// file: lib/common/plugin/capture.js +define("cordova/plugin/capture", function(require, exports, module) { + +var exec = require('cordova/exec'), + MediaFile = require('cordova/plugin/MediaFile'); + +/** + * Launches a capture of different types. + * + * @param (DOMString} type + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +function _capture(type, successCallback, errorCallback, options) { + var win = function(pluginResult) { + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult[i].name; + mediaFile.fullPath = pluginResult[i].fullPath; + mediaFile.type = pluginResult[i].type; + mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; + mediaFile.size = pluginResult[i].size; + mediaFiles.push(mediaFile); + } + successCallback(mediaFiles); + }; + exec(win, errorCallback, "Capture", type, [options]); +} +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +} + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + _capture("captureAudio", successCallback, errorCallback, options); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + _capture("captureImage", successCallback, errorCallback, options); +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + _capture("captureVideo", successCallback, errorCallback, options); +}; + + +module.exports = new Capture(); + +}); + +// file: lib/common/plugin/compass.js +define("cordova/plugin/compass", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + utils = require('cordova/utils'), + CompassHeading = require('cordova/plugin/CompassHeading'), + CompassError = require('cordova/plugin/CompassError'), + timers = {}, + compass = { + /** + * Asynchronously acquires the current heading. + * @param {Function} successCallback The function to call when the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {CompassOptions} options The options for getting the heading data (not used). + */ + getCurrentHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.getCurrentHeading', arguments); + + var win = function(result) { + var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + successCallback(ch); + }; + var fail = errorCallback && function(code) { + var ce = new CompassError(code); + errorCallback(ce); + }; + + // Get heading + exec(win, fail, "Compass", "getHeading", [options]); + }, + + /** + * Asynchronously acquires the heading repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {HeadingOptions} options The options for getting the heading data + * such as timeout and the frequency of the watch. For iOS, filter parameter + * specifies to watch via a distance filter rather than time. + */ + watchHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.watchHeading', arguments); + // Default interval (100 msec) + var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; + var filter = (options !== undefined && options.filter !== undefined) ? options.filter : 0; + + var id = utils.createUUID(); + if (filter > 0) { + // is an iOS request for watch by filter, no timer needed + timers[id] = "iOS"; + compass.getCurrentHeading(successCallback, errorCallback, options); + } else { + // Start watch timer to get headings + timers[id] = window.setInterval(function() { + compass.getCurrentHeading(successCallback, errorCallback); + }, frequency); + } + + return id; + }, + + /** + * Clears the specified heading watch. + * @param {String} watchId The ID of the watch returned from #watchHeading. + */ + clearWatch:function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + if (timers[id] != "iOS") { + clearInterval(timers[id]); + } else { + // is iOS watch by filter so call into device to stop + exec(null, null, "Compass", "stopHeading", []); + } + delete timers[id]; + } + } + }; + +module.exports = compass; + +}); + +// file: lib/common/plugin/console-via-logger.js +define("cordova/plugin/console-via-logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ + +var logger = require("cordova/plugin/logger"); +var utils = require("cordova/utils"); + +//------------------------------------------------------------------------------ +// object that we're exporting +//------------------------------------------------------------------------------ +var console = module.exports; + +//------------------------------------------------------------------------------ +// copy of the original console object +//------------------------------------------------------------------------------ +var WinConsole = window.console; + +//------------------------------------------------------------------------------ +// whether to use the logger +//------------------------------------------------------------------------------ +var UseLogger = false; + +//------------------------------------------------------------------------------ +// Timers +//------------------------------------------------------------------------------ +var Timers = {}; + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +function noop() {} + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +console.useLogger = function (value) { + if (arguments.length) UseLogger = !!value; + + if (UseLogger) { + if (logger.useConsole()) { + throw new Error("console and logger are too intertwingly"); + } + } + + return UseLogger; +}; + +//------------------------------------------------------------------------------ +console.log = function() { + if (logger.useConsole()) return; + logger.log.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.error = function() { + if (logger.useConsole()) return; + logger.error.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.warn = function() { + if (logger.useConsole()) return; + logger.warn.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.info = function() { + if (logger.useConsole()) return; + logger.info.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.debug = function() { + if (logger.useConsole()) return; + logger.debug.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.assert = function(expression) { + if (expression) return; + + var message = utils.vformat(arguments[1], [].slice.call(arguments, 2)); + console.log("ASSERT: " + message); +}; + +//------------------------------------------------------------------------------ +console.clear = function() {}; + +//------------------------------------------------------------------------------ +console.dir = function(object) { + console.log("%o", object); +}; + +//------------------------------------------------------------------------------ +console.dirxml = function(node) { + console.log(node.innerHTML); +}; + +//------------------------------------------------------------------------------ +console.trace = noop; + +//------------------------------------------------------------------------------ +console.group = console.log; + +//------------------------------------------------------------------------------ +console.groupCollapsed = console.log; + +//------------------------------------------------------------------------------ +console.groupEnd = noop; + +//------------------------------------------------------------------------------ +console.time = function(name) { + Timers[name] = new Date().valueOf(); +}; + +//------------------------------------------------------------------------------ +console.timeEnd = function(name) { + var timeStart = Timers[name]; + if (!timeStart) { + console.warn("unknown timer: " + name); + return; + } + + var timeElapsed = new Date().valueOf() - timeStart; + console.log(name + ": " + timeElapsed + "ms"); +}; + +//------------------------------------------------------------------------------ +console.timeStamp = noop; + +//------------------------------------------------------------------------------ +console.profile = noop; + +//------------------------------------------------------------------------------ +console.profileEnd = noop; + +//------------------------------------------------------------------------------ +console.count = noop; + +//------------------------------------------------------------------------------ +console.exception = console.log; + +//------------------------------------------------------------------------------ +console.table = function(data, columns) { + console.log("%o", data); +}; + +//------------------------------------------------------------------------------ +// return a new function that calls both functions passed as args +//------------------------------------------------------------------------------ +function wrappedOrigCall(orgFunc, newFunc) { + return function() { + var args = [].slice.call(arguments); + try { orgFunc.apply(WinConsole, args); } catch (e) {} + try { newFunc.apply(console, args); } catch (e) {} + }; +} + +//------------------------------------------------------------------------------ +// For every function that exists in the original console object, that +// also exists in the new console object, wrap the new console method +// with one that calls both +//------------------------------------------------------------------------------ +for (var key in console) { + if (typeof WinConsole[key] == "function") { + console[key] = wrappedOrigCall(WinConsole[key], console[key]); + } +} + +}); + +// file: lib/common/plugin/contacts.js +define("cordova/plugin/contacts", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'), + Contact = require('cordova/plugin/Contact'); + +/** +* Represents a group of Contacts. +* @constructor +*/ +var contacts = { + /** + * Returns an array of Contacts matching the search criteria. + * @param fields that should be searched + * @param successCB success callback + * @param errorCB error callback + * @param {ContactFindOptions} options that can be applied to contact searching + * @return array of Contacts matching search criteria + */ + find:function(fields, successCB, errorCB, options) { + argscheck.checkArgs('afFO', 'contacts.find', arguments); + if (!fields.length) { + errorCB && errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); + } else { + var win = function(result) { + var cs = []; + for (var i = 0, l = result.length; i < l; i++) { + cs.push(contacts.create(result[i])); + } + successCB(cs); + }; + exec(win, errorCB, "Contacts", "search", [fields, options]); + } + }, + + /** + * This function creates a new contact, but it does not persist the contact + * to device storage. To persist the contact to device storage, invoke + * contact.save(). + * @param properties an object whose properties will be examined to create a new Contact + * @returns new Contact object + */ + create:function(properties) { + argscheck.checkArgs('O', 'contacts.create', arguments); + var contact = new Contact(); + for (var i in properties) { + if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { + contact[i] = properties[i]; + } + } + return contact; + } +}; + +module.exports = contacts; + +}); + +// file: lib/common/plugin/device.js +define("cordova/plugin/device", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +// Tell cordova channel to wait on the CordovaInfoReady event +channel.waitForInitialization('onCordovaInfoReady'); + +/** + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * phone, etc. + * @constructor + */ +function Device() { + this.available = false; + this.platform = null; + this.version = null; + this.name = null; + this.uuid = null; + this.cordova = null; + this.model = null; + + var me = this; + + channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.name = info.name; + me.uuid = info.uuid; + me.cordova = info.cordova; + me.model = info.model; + channel.onCordovaInfoReady.fire(); + },function(e) { + me.available = false; + utils.alert("[ERROR] Error initializing Cordova: " + e); + }); + }); +} + +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + */ +Device.prototype.getInfo = function(successCallback, errorCallback) { + argscheck.checkArgs('fF', 'Device.getInfo', arguments); + exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +module.exports = new Device(); + +}); + +// file: lib/common/plugin/echo.js +define("cordova/plugin/echo", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + * @param message The string to be echoed. + * @param forceAsync Whether to force an async return value (for testing native->js bridge). + */ +module.exports = function(successCallback, errorCallback, message, forceAsync) { + var action = forceAsync ? 'echoAsync' : 'echo'; + exec(successCallback, errorCallback, "Echo", action, [message]); +}; + + +}); + +// file: lib/common/plugin/geolocation.js +define("cordova/plugin/geolocation", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + PositionError = require('cordova/plugin/PositionError'), + Position = require('cordova/plugin/Position'); + +var timers = {}; // list of timers in use + +// Returns default params, overrides if provided with values +function parseParameters(options) { + var opt = { + maximumAge: 0, + enableHighAccuracy: false, + timeout: Infinity + }; + + if (options) { + if (options.maximumAge !== undefined && !isNaN(options.maximumAge) && options.maximumAge > 0) { + opt.maximumAge = options.maximumAge; + } + if (options.enableHighAccuracy !== undefined) { + opt.enableHighAccuracy = options.enableHighAccuracy; + } + if (options.timeout !== undefined && !isNaN(options.timeout)) { + if (options.timeout < 0) { + opt.timeout = 0; + } else { + opt.timeout = options.timeout; + } + } + } + + return opt; +} + +// Returns a timeout failure, closed over a specified timeout value and error callback. +function createTimeout(errorCallback, timeout) { + var t = setTimeout(function() { + clearTimeout(t); + t = null; + errorCallback({ + code:PositionError.TIMEOUT, + message:"Position retrieval timed out." + }); + }, timeout); + return t; +} + +var geolocation = { + lastPosition:null, // reference to last known (cached) position returned + /** + * Asynchronously acquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ + getCurrentPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + // Timer var that will fire an error callback if no position is retrieved from native + // before the "timeout" param provided expires + var timeoutTimer = {timer:null}; + + var win = function(p) { + clearTimeout(timeoutTimer.timer); + if (!(timeoutTimer.timer)) { + // Timeout already happened, or native fired error callback for + // this geo request. + // Don't continue with success callback. + return; + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + var fail = function(e) { + clearTimeout(timeoutTimer.timer); + timeoutTimer.timer = null; + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + // Check our cached position, if its timestamp difference with current time is less than the maximumAge, then just + // fire the success callback with the cached position. + if (geolocation.lastPosition && options.maximumAge && (((new Date()).getTime() - geolocation.lastPosition.timestamp.getTime()) <= options.maximumAge)) { + successCallback(geolocation.lastPosition); + // If the cached position check failed and the timeout was set to 0, error out with a TIMEOUT error object. + } else if (options.timeout === 0) { + fail({ + code:PositionError.TIMEOUT, + message:"timeout value in PositionOptions set to 0 and no cached Position object available, or cached Position object's age exceeds provided PositionOptions' maximumAge parameter." + }); + // Otherwise we have to call into native to retrieve a position. + } else { + if (options.timeout !== Infinity) { + // If the timeout value was not set to Infinity (default), then + // set up a timeout function that will fire the error callback + // if no successful position was retrieved before timeout expired. + timeoutTimer.timer = createTimeout(fail, options.timeout); + } else { + // This is here so the check in the win function doesn't mess stuff up + // may seem weird but this guarantees timeoutTimer is + // always truthy before we call into native + timeoutTimer.timer = true; + } + exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.maximumAge]); + } + return timeoutTimer; + }, + /** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + var id = utils.createUUID(); + + // Tell device to get a position ASAP, and also retrieve a reference to the timeout timer generated in getCurrentPosition + timers[id] = geolocation.getCurrentPosition(successCallback, errorCallback, options); + + var fail = function(e) { + clearTimeout(timers[id].timer); + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + var win = function(p) { + clearTimeout(timers[id].timer); + if (options.timeout !== Infinity) { + timers[id].timer = createTimeout(fail, options.timeout); + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + + exec(win, fail, "Geolocation", "addWatch", [id, options.enableHighAccuracy]); + + return id; + }, + /** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ + clearWatch:function(id) { + if (id && timers[id] !== undefined) { + clearTimeout(timers[id].timer); + timers[id].timer = false; + exec(null, null, "Geolocation", "clearWatch", [id]); + } + } +}; + +module.exports = geolocation; + +}); + +// file: lib/common/plugin/globalization.js +define("cordova/plugin/globalization", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + GlobalizationError = require('cordova/plugin/GlobalizationError'); + +var globalization = { + +/** +* Returns the string identifier for the client's current language. +* It returns the language identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the language, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The language identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getPreferredLanguage(function (language) {alert('language:' + language.value + '\n');}, +* function () {}); +*/ +getPreferredLanguage:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getPreferredLanguage', arguments); + exec(successCB, failureCB, "Globalization","getPreferredLanguage", []); +}, + +/** +* Returns the string identifier for the client's current locale setting. +* It returns the locale identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the locale, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The locale identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getLocaleName(function (locale) {alert('locale:' + locale.value + '\n');}, +* function () {}); +*/ +getLocaleName:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getLocaleName', arguments); + exec(successCB, failureCB, "Globalization","getLocaleName", []); +}, + + +/** +* Returns a date formatted as a string according to the client's user preferences and +* calendar using the time zone of the client. It returns the formatted date string to the +* successCB callback with a properties object as a parameter. If there is an error +* formatting the date, then the errorCB callback is invoked. +* +* The defaults are: formatLenght="short" and selector="date and time" +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.value {String}: The localized date string +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.dateToString(new Date(), +* function (date) {alert('date:' + date.value + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {formatLength:'short'}); +*/ +dateToString:function(date, successCB, failureCB, options) { + argscheck.checkArgs('dfFO', 'Globalization.dateToString', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "dateToString", [{"date": dateValue, "options": options}]); +}, + + +/** +* Parses a date formatted as a string according to the client's user +* preferences and calendar using the time zone of the client and returns +* the corresponding date object. It returns the date to the successCB +* callback with a properties object as a parameter. If there is an error +* parsing the date string, then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {String} dateString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.year {Number}: The four digit year +* Object.month {Number}: The month from (0 - 11) +* Object.day {Number}: The day from (1 - 31) +* Object.hour {Number}: The hour from (0 - 23) +* Object.minute {Number}: The minute from (0 - 59) +* Object.second {Number}: The second from (0 - 59) +* Object.millisecond {Number}: The milliseconds (from 0 - 999), +* not available on all platforms +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToDate('4/11/2011', +* function (date) { alert('Month:' + date.month + '\n' + +* 'Day:' + date.day + '\n' + +* 'Year:' + date.year + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {selector:'date'}); +*/ +stringToDate:function(dateString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToDate', arguments); + exec(successCB, failureCB, "Globalization", "stringToDate", [{"dateString": dateString, "options": options}]); +}, + + +/** +* Returns a pattern string for formatting and parsing dates according to the client's +* user preferences. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, +* then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.pattern {String}: The date and time pattern for formatting and parsing dates. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.timezone {String}: The abbreviated name of the time zone on the client +* Object.utc_offset {Number}: The current difference in seconds between the client's +* time zone and coordinated universal time. +* Object.dst_offset {Number}: The current daylight saving time offset in seconds +* between the client's non-daylight saving's time zone +* and the client's daylight saving's time zone. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getDatePattern( +* function (date) {alert('pattern:' + date.pattern + '\n');}, +* function () {}, +* {formatLength:'short'}); +*/ +getDatePattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDatePattern', arguments); + exec(successCB, failureCB, "Globalization", "getDatePattern", [{"options": options}]); +}, + + +/** +* Returns an array of either the names of the months or days of the week +* according to the client's user preferences and calendar. It returns the array of names to the +* successCB callback with a properties object as a parameter. If there is an error obtaining the +* names, then the errorCB callback is invoked. +* +* The defaults are: type="wide" and item="months" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'narrow' or 'wide' +* item {String}: 'months', or 'days' +* +* @return Object.value {Array{String}}: The array of names starting from either +* the first month in the year or the +* first day of the week. +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getDateNames(function (names) { +* for(var i = 0; i < names.value.length; i++) { +* alert('Month:' + names.value[i] + '\n');}}, +* function () {}); +*/ +getDateNames:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDateNames', arguments); + exec(successCB, failureCB, "Globalization", "getDateNames", [{"options": options}]); +}, + +/** +* Returns whether daylight savings time is in effect for a given date using the client's +* time zone and calendar. It returns whether or not daylight savings time is in effect +* to the successCB callback with a properties object as a parameter. If there is an error +* reading the date, then the errorCB callback is invoked. +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.dst {Boolean}: The value "true" indicates that daylight savings time is +* in effect for the given date and "false" indicate that it is not. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.isDayLightSavingsTime(new Date(), +* function (date) {alert('dst:' + date.dst + '\n');} +* function () {}); +*/ +isDayLightSavingsTime:function(date, successCB, failureCB) { + argscheck.checkArgs('dfF', 'Globalization.isDayLightSavingsTime', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "isDayLightSavingsTime", [{"date": dateValue}]); +}, + +/** +* Returns the first day of the week according to the client's user preferences and calendar. +* The days of the week are numbered starting from 1 where 1 is considered to be Sunday. +* It returns the day to the successCB callback with a properties object as a parameter. +* If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {Number}: The number of the first day of the week. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getFirstDayOfWeek(function (day) +* { alert('Day:' + day.value + '\n');}, +* function () {}); +*/ +getFirstDayOfWeek:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getFirstDayOfWeek', arguments); + exec(successCB, failureCB, "Globalization", "getFirstDayOfWeek", []); +}, + + +/** +* Returns a number formatted as a string according to the client's user preferences. +* It returns the formatted number string to the successCB callback with a properties object as a +* parameter. If there is an error formatting the number, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Number} number +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {String}: The formatted number string. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.numberToString(3.25, +* function (number) {alert('number:' + number.value + '\n');}, +* function () {}, +* {type:'decimal'}); +*/ +numberToString:function(number, successCB, failureCB, options) { + argscheck.checkArgs('nfFO', 'Globalization.numberToString', arguments); + exec(successCB, failureCB, "Globalization", "numberToString", [{"number": number, "options": options}]); +}, + +/** +* Parses a number formatted as a string according to the client's user preferences and +* returns the corresponding number. It returns the number to the successCB callback with a +* properties object as a parameter. If there is an error parsing the number string, then +* the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {String} numberString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {Number}: The parsed number. +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToNumber('1234.56', +* function (number) {alert('Number:' + number.value + '\n');}, +* function () { alert('Error parsing number');}); +*/ +stringToNumber:function(numberString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToNumber', arguments); + exec(successCB, failureCB, "Globalization", "stringToNumber", [{"numberString": numberString, "options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing numbers according to the client's user +* preferences. It returns the pattern to the successCB callback with a properties object as a +* parameter. If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.pattern {String}: The number pattern for formatting and parsing numbers. +* The patterns follow Unicode Technical Standard #35. +* http://unicode.org/reports/tr35/tr35-4.html +* Object.symbol {String}: The symbol to be used when formatting and parsing +* e.g., percent or currency symbol. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting numbers. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.positive {String}: The symbol to use for positive numbers when parsing and formatting. +* Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getNumberPattern( +* function (pattern) {alert('Pattern:' + pattern.pattern + '\n');}, +* function () {}); +*/ +getNumberPattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getNumberPattern', arguments); + exec(successCB, failureCB, "Globalization", "getNumberPattern", [{"options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing currency values according to the client's +* user preferences and ISO 4217 currency code. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, then the errorCB +* callback is invoked. +* +* @param {String} currencyCode +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.pattern {String}: The currency pattern for formatting and parsing currency values. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.code {String}: The ISO 4217 currency code for the pattern. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting currency. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.getCurrencyPattern('EUR', +* function (currency) {alert('Pattern:' + currency.pattern + '\n');} +* function () {}); +*/ +getCurrencyPattern:function(currencyCode, successCB, failureCB) { + argscheck.checkArgs('sfF', 'Globalization.getCurrencyPattern', arguments); + exec(successCB, failureCB, "Globalization", "getCurrencyPattern", [{"currencyCode": currencyCode}]); +} + +}; + +module.exports = globalization; + +}); + +// file: lib/ios/plugin/ios/Contact.js +define("cordova/plugin/ios/Contact", function(require, exports, module) { + +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'); + +/** + * Provides iOS Contact.display API. + */ +module.exports = { + display : function(errorCB, options) { + /* + * Display a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + */ + + if (this.id === null) { + if (typeof errorCB === "function") { + var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); + errorCB(errorObj); + } + } + else { + exec(null, errorCB, "Contacts","displayContact", [this.id, options]); + } + } +}; + +}); + +// file: lib/ios/plugin/ios/Entry.js +define("cordova/plugin/ios/Entry", function(require, exports, module) { + +module.exports = { + toURL:function() { + // TODO: refactor path in a cross-platform way so we can eliminate + // these kinds of platform-specific hacks. + return "file://localhost" + this.fullPath; + }, + toURI: function() { + console.log("DEPRECATED: Update your code to use 'toURL'"); + return "file://localhost" + this.fullPath; + } +}; + +}); + +// file: lib/ios/plugin/ios/FileReader.js +define("cordova/plugin/ios/FileReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + FileReader = require('cordova/plugin/FileReader'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +module.exports = { + readAsText:function(file, encoding) { + // Figure out pathing + this.fileName = ''; + if (typeof file.fullPath === 'undefined') { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // Already loading something + if (this.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart(new ProgressEvent("loadstart", {target:this})); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + + var me = this; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save result + me.result = decodeURIComponent(r); + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + // null result + me.result = null; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + "File", "readAsText", [this.fileName, enc]); + } +}; + +}); + +// file: lib/ios/plugin/ios/console.js +define("cordova/plugin/ios/console", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * create a nice string for an object + */ +function stringify(message) { + try { + if (typeof message === "object" && JSON && JSON.stringify) { + try { + return JSON.stringify(message); + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + } else { + return message.toString(); + } + } catch (e) { + return e.toString(); + } +} + +/** + * Wrapper one of the console logging methods, so that + * the Cordova logging native is called, then the original. + */ +function wrappedMethod(console, method) { + var origMethod = console[method]; + + return function(message) { + exec(null, null, + 'Debug Console', 'log', + [ stringify(message), { logLevel: method.toUpperCase() } ] + ); + + if (!origMethod) return; + + origMethod.apply(console, arguments); + }; +} + +var console = window.console || {}; + +// 2012-10-06 pmuellr - marking setLevel() method and logLevel property +// on console as deprecated; +// it didn't do anything useful, since the level constants weren't accessible +// to anyone + +console.setLevel = function() {}; +console.logLevel = 0; + +// wrapper the logging messages + +var methods = ["log", "debug", "info", "warn", "error"]; + +for (var i=0; i<methods.length; i++) { + var method = methods[i]; + + console[method] = wrappedMethod(console, method); +} + +module.exports = console; + +}); + +// file: lib/ios/plugin/ios/contacts.js +define("cordova/plugin/ios/contacts", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Provides iOS enhanced contacts API. + */ +module.exports = { + newContactUI : function(successCallback) { + /* + * Create a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * returns: the id of the created contact as param to successCallback + */ + exec(successCallback, null, "Contacts","newContact", []); + }, + chooseContact : function(successCallback, options) { + /* + * Select a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + * fields: array of fields to return in contact object (see ContactOptions.fields) + * + * @returns + * id of contact selected + * ContactObject + * if no fields provided contact contains just id information + * if fields provided contact object contains information for the specified fields + * + */ + var win = function(result) { + var fullContact = require('cordova/plugin/contacts').create(result); + successCallback(fullContact.id, fullContact); + }; + exec(win, null, "Contacts","chooseContact", [options]); + } +}; + +}); + +// file: lib/ios/plugin/ios/notification.js +define("cordova/plugin/ios/notification", function(require, exports, module) { + +var Media = require('cordova/plugin/Media'); + +module.exports = { + beep:function(count) { + (new Media('beep.wav')).play(); + } +}; + +}); + +// file: lib/common/plugin/logger.js +define("cordova/plugin/logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ +// The logger module exports the following properties/functions: +// +// LOG - constant for the level LOG +// ERROR - constant for the level ERROR +// WARN - constant for the level WARN +// INFO - constant for the level INFO +// DEBUG - constant for the level DEBUG +// logLevel() - returns current log level +// logLevel(value) - sets and returns a new log level +// useConsole() - returns whether logger is using console +// useConsole(value) - sets and returns whether logger is using console +// log(message,...) - logs a message at level LOG +// error(message,...) - logs a message at level ERROR +// warn(message,...) - logs a message at level WARN +// info(message,...) - logs a message at level INFO +// debug(message,...) - logs a message at level DEBUG +// logLevel(level,message,...) - logs a message specified level +// +//------------------------------------------------------------------------------ + +var logger = exports; + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var UseConsole = true; +var Queued = []; +var DeviceReady = false; +var CurrentLevel; + +/** + * Logging levels + */ + +var Levels = [ + "LOG", + "ERROR", + "WARN", + "INFO", + "DEBUG" +]; + +/* + * add the logging levels to the logger object and + * to a separate levelsMap object for testing + */ + +var LevelsMap = {}; +for (var i=0; i<Levels.length; i++) { + var level = Levels[i]; + LevelsMap[level] = i; + logger[level] = level; +} + +CurrentLevel = LevelsMap.WARN; + +/** + * Getter/Setter for the logging level + * + * Returns the current logging level. + * + * When a value is passed, sets the logging level to that value. + * The values should be one of the following constants: + * logger.LOG + * logger.ERROR + * logger.WARN + * logger.INFO + * logger.DEBUG + * + * The value used determines which messages get printed. The logging + * values above are in order, and only messages logged at the logging + * level or above will actually be displayed to the user. E.g., the + * default level is WARN, so only messages logged with LOG, ERROR, or + * WARN will be displayed; INFO and DEBUG messages will be ignored. + */ +logger.level = function (value) { + if (arguments.length) { + if (LevelsMap[value] === null) { + throw new Error("invalid logging level: " + value); + } + CurrentLevel = LevelsMap[value]; + } + + return Levels[CurrentLevel]; +}; + +/** + * Getter/Setter for the useConsole functionality + * + * When useConsole is true, the logger will log via the + * browser 'console' object. Otherwise, it will use the + * native Logger plugin. + */ +logger.useConsole = function (value) { + if (arguments.length) UseConsole = !!value; + + if (UseConsole) { + if (typeof console == "undefined") { + throw new Error("global console object is not defined"); + } + + if (typeof console.log != "function") { + throw new Error("global console object does not have a log function"); + } + + if (typeof console.useLogger == "function") { + if (console.useLogger()) { + throw new Error("console and logger are too intertwingly"); + } + } + } + + return UseConsole; +}; + +/** + * Logs a message at the LOG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.log = function(message) { logWithArgs("LOG", arguments); }; + +/** + * Logs a message at the ERROR level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.error = function(message) { logWithArgs("ERROR", arguments); }; + +/** + * Logs a message at the WARN level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.warn = function(message) { logWithArgs("WARN", arguments); }; + +/** + * Logs a message at the INFO level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.info = function(message) { logWithArgs("INFO", arguments); }; + +/** + * Logs a message at the DEBUG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.debug = function(message) { logWithArgs("DEBUG", arguments); }; + +// log at the specified level with args +function logWithArgs(level, args) { + args = [level].concat([].slice.call(args)); + logger.logLevel.apply(logger, args); +} + +/** + * Logs a message at the specified level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.logLevel = function(level, message /* , ... */) { + // format the message with the parameters + var formatArgs = [].slice.call(arguments, 2); + message = utils.vformat(message, formatArgs); + + if (LevelsMap[level] === null) { + throw new Error("invalid logging level: " + level); + } + + if (LevelsMap[level] > CurrentLevel) return; + + // queue the message if not yet at deviceready + if (!DeviceReady && !UseConsole) { + Queued.push([level, message]); + return; + } + + // if not using the console, use the native logger + if (!UseConsole) { + exec(null, null, "Logger", "logLevel", [level, message]); + return; + } + + // make sure console is not using logger + if (console.__usingCordovaLogger) { + throw new Error("console and logger are too intertwingly"); + } + + // log to the console + switch (level) { + case logger.LOG: console.log(message); break; + case logger.ERROR: console.log("ERROR: " + message); break; + case logger.WARN: console.log("WARN: " + message); break; + case logger.INFO: console.log("INFO: " + message); break; + case logger.DEBUG: console.log("DEBUG: " + message); break; + } +}; + +// when deviceready fires, log queued messages +logger.__onDeviceReady = function() { + if (DeviceReady) return; + + DeviceReady = true; + + for (var i=0; i<Queued.length; i++) { + var messageArgs = Queued[i]; + logger.logLevel(messageArgs[0], messageArgs[1]); + } + + Queued = null; +}; + +// add a deviceready event to log queued messages +document.addEventListener("deviceready", logger.__onDeviceReady, false); + +}); + +// file: lib/common/plugin/network.js +define("cordova/plugin/network", function(require, exports, module) { + +var exec = require('cordova/exec'), + cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'); + +// Link the onLine property with the Cordova-supplied network info. +// This works because we clobber the naviagtor object with our own +// object in bootstrap.js. +if (typeof navigator != 'undefined') { + utils.defineGetter(navigator, 'onLine', function() { + return this.connection.type != 'none'; + }); +} + +function NetworkConnection() { + this.type = 'unknown'; +} + +/** + * Get connection info + * + * @param {Function} successCallback The function to call when the Connection data is available + * @param {Function} errorCallback The function to call when there is an error getting the Connection data. (OPTIONAL) + */ +NetworkConnection.prototype.getInfo = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "NetworkStatus", "getConnectionInfo", []); +}; + +var me = new NetworkConnection(); +var timerId = null; +var timeout = 500; + +channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + me.type = info; + if (info === "none") { + // set a timer if still offline at the end of timer send the offline event + timerId = setTimeout(function(){ + cordova.fireDocumentEvent("offline"); + timerId = null; + }, timeout); + } else { + // If there is a current offline event pending clear it + if (timerId !== null) { + clearTimeout(timerId); + timerId = null; + } + cordova.fireDocumentEvent("online"); + } + + // should only fire this once + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + }, + function (e) { + // If we can't get the network info we should still tell Cordova + // to fire the deviceready event. + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + console.log("Error initializing Network Connection: " + e); + }); +}); + +module.exports = me; + +}); + +// file: lib/common/plugin/notification.js +define("cordova/plugin/notification", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Provides access to notifications on the device. + */ + +module.exports = { + + /** + * Open a native alert dialog, with a customizable title and button text. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} completeCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Alert) + * @param {String} buttonLabel Label of the close button (default: OK) + */ + alert: function(message, completeCallback, title, buttonLabel) { + var _title = (title || "Alert"); + var _buttonLabel = (buttonLabel || "OK"); + exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]); + }, + + /** + * Open a native confirm dialog, with a customizable title and button text. + * The result that the user selects is returned to the result callback. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Confirm) + * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') + */ + confirm: function(message, resultCallback, title, buttonLabels) { + var _title = (title || "Confirm"); + var _buttonLabels = (buttonLabels || "OK,Cancel"); + exec(resultCallback, null, "Notification", "confirm", [message, _title, _buttonLabels]); + }, + + /** + * Causes the device to vibrate. + * + * @param {Integer} mills The number of milliseconds to vibrate for. + */ + vibrate: function(mills) { + exec(null, null, "Notification", "vibrate", [mills]); + }, + + /** + * Causes the device to beep. + * On Android, the default notification ringtone is played "count" times. + * + * @param {Integer} count The number of beeps. + */ + beep: function(count) { + exec(null, null, "Notification", "beep", [count]); + } +}; + +}); + +// file: lib/common/plugin/requestFileSystem.js +define("cordova/plugin/requestFileSystem", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + FileError = require('cordova/plugin/FileError'), + FileSystem = require('cordova/plugin/FileSystem'), + exec = require('cordova/exec'); + +/** + * Request a file system in which to store application data. + * @param type local file system type + * @param size indicates how much storage space, in bytes, the application expects to need + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + */ +var requestFileSystem = function(type, size, successCallback, errorCallback) { + argscheck.checkArgs('nnFF', 'requestFileSystem', arguments); + var fail = function(code) { + errorCallback && errorCallback(new FileError(code)); + }; + + if (type < 0 || type > 3) { + fail(FileError.SYNTAX_ERR); + } else { + // if successful, return a FileSystem object + var success = function(file_system) { + if (file_system) { + if (successCallback) { + // grab the name and root from the file system object + var result = new FileSystem(file_system.name, file_system.root); + successCallback(result); + } + } + else { + // no FileSystem object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + exec(success, fail, "File", "requestFileSystem", [type, size]); + } +}; + +module.exports = requestFileSystem; + +}); + +// file: lib/common/plugin/resolveLocalFileSystemURI.js +define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + DirectoryEntry = require('cordova/plugin/DirectoryEntry'), + FileEntry = require('cordova/plugin/FileEntry'), + FileError = require('cordova/plugin/FileError'), + exec = require('cordova/exec'); + +/** + * Look up file system Entry referred to by local URI. + * @param {DOMString} uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ +module.exports = function(uri, successCallback, errorCallback) { + argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments); + // error callback + var fail = function(error) { + errorCallback && errorCallback(new FileError(error)); + }; + // sanity check for 'not:valid:filename' + if(!uri || uri.split(":").length > 2) { + setTimeout( function() { + fail(FileError.ENCODING_ERR); + },0); + return; + } + // if successful, return either a file or directory entry + var success = function(entry) { + var result; + if (entry) { + if (successCallback) { + // create appropriate Entry object + result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); +}; + +}); + +// file: lib/common/plugin/splashscreen.js +define("cordova/plugin/splashscreen", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var splashscreen = { + show:function() { + exec(null, null, "SplashScreen", "show", []); + }, + hide:function() { + exec(null, null, "SplashScreen", "hide", []); + } +}; + +module.exports = splashscreen; + +}); + +// file: lib/common/utils.js +define("cordova/utils", function(require, exports, module) { + +var utils = exports; + +/** + * Defines a property getter for obj[key]. + */ +utils.defineGetter = function(obj, key, func) { + if (Object.defineProperty) { + Object.defineProperty(obj, key, { get: func }); + } else { + obj.__defineGetter__(key, func); + } +}; + +utils.arrayIndexOf = function(a, item) { + if (a.indexOf) { + return a.indexOf(item); + } + var len = a.length; + for (var i = 0; i < len; ++i) { + if (a[i] == item) { + return i; + } + } + return -1; +}; + +/** + * Returns whether the item was found in the array. + */ +utils.arrayRemove = function(a, item) { + var index = utils.arrayIndexOf(a, item); + if (index != -1) { + a.splice(index, 1); + } + return index != -1; +}; + +/** + * Returns an indication of whether the argument is an array or not + */ +utils.isArray = function(a) { + return Object.prototype.toString.call(a) == '[object Array]'; +}; + +/** + * Returns an indication of whether the argument is a Date or not + */ +utils.isDate = function(d) { + return Object.prototype.toString.call(d) == '[object Date]'; +}; + +/** + * Does a deep clone of the object. + */ +utils.clone = function(obj) { + if(!obj || typeof obj == 'function' || utils.isDate(obj) || typeof obj != 'object') { + return obj; + } + + var retVal, i; + + if(utils.isArray(obj)){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(utils.clone(obj[i])); + } + return retVal; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] != obj[i]) { + retVal[i] = utils.clone(obj[i]); + } + } + return retVal; +}; + +/** + * Returns a wrapped version of the function + */ +utils.close = function(context, func, params) { + if (typeof params == 'undefined') { + return function() { + return func.apply(context, arguments); + }; + } else { + return function() { + return func.apply(context, params); + }; + } +}; + +/** + * Create a UUID + */ +utils.createUUID = function() { + return UUIDcreatePart(4) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(6); +}; + +/** + * Extends a child object from a parent object using classical inheritance + * pattern. + */ +utils.extend = (function() { + // proxy used to establish prototype chain + var F = function() {}; + // extend Child from Parent + return function(Child, Parent) { + F.prototype = Parent.prototype; + Child.prototype = new F(); + Child.__super__ = Parent.prototype; + Child.prototype.constructor = Child; + }; +}()); + +/** + * Alerts a message in any available way: alert or console.log. + */ +utils.alert = function(msg) { + if (window.alert) { + window.alert(msg); + } else if (console && console.log) { + console.log(msg); + } +}; + +/** + * Formats a string and arguments following it ala sprintf() + * + * see utils.vformat() for more information + */ +utils.format = function(formatString /* ,... */) { + var args = [].slice.call(arguments, 1); + return utils.vformat(formatString, args); +}; + +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +utils.vformat = function(formatString, args) { + if (formatString === null || formatString === undefined) return ""; + if (arguments.length == 1) return formatString.toString(); + if (typeof formatString != "string") return formatString.toString(); + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var arg = args.shift(); + var match = pattern.exec(rest); + + if (!match) break; + + rest = match[3]; + + result.push(match[1]); + + if (match[2] == '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(formatted(arg, match[2])); + } + + result.push(rest); + + return result.join(''); +}; + +//------------------------------------------------------------------------------ +function UUIDcreatePart(length) { + var uuidpart = ""; + for (var i=0; i<length; i++) { + var uuidchar = parseInt((Math.random() * 256), 10).toString(16); + if (uuidchar.length == 1) { + uuidchar = "0" + uuidchar; + } + uuidpart += uuidchar; + } + return uuidpart; +} + +//------------------------------------------------------------------------------ +function formatted(object, formatChar) { + + try { + switch(formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + +}); + + +window.cordova = require('cordova'); + +// file: lib/scripts/bootstrap.js + +(function (context) { + // Replace navigator before any modules are required(), to ensure it happens as soon as possible. + // We replace it so that properties that can't be clobbered can instead be overridden. + if (context.navigator) { + function CordovaNavigator() {} + CordovaNavigator.prototype = context.navigator; + context.navigator = new CordovaNavigator(); + } + + var channel = require("cordova/channel"), + _self = { + boot: function () { + /** + * Create all cordova objects once page has fully loaded and native side is ready. + */ + channel.join(function() { + var builder = require('cordova/builder'), + base = require('cordova/common'), + platform = require('cordova/platform'); + + // Drop the common globals into the window object, but be nice and don't overwrite anything. + builder.buildIntoButDoNotClobber(base.defaults, context); + builder.buildIntoAndClobber(base.clobbers, context); + builder.buildIntoAndMerge(base.merges, context); + + builder.buildIntoButDoNotClobber(platform.defaults, context); + builder.buildIntoAndClobber(platform.clobbers, context); + builder.buildIntoAndMerge(platform.merges, context); + + // Call the platform-specific initialization + platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once all constructors have run and + // cordova info has been received from native side. + channel.join(function() { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); + + }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); + } + }; + + // boot up once native side is ready + channel.onNativeReady.subscribe(_self.boot); + + // _nativeReady is global variable that the native side can set + // to signify that the native code is ready. It is a global since + // it may be called before any cordova JS is ready. + if (window._nativeReady) { + channel.onNativeReady.fire(); + } + +}(window)); + + +})();
\ No newline at end of file |