aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xiPhone/CordovaLib/Classes/CDV.h58
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVAccelerometer.h39
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVAccelerometer.m128
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVAvailability.h74
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVBattery.h40
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVBattery.m152
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCamera.h90
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCamera.m533
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCapture.h118
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCapture.m850
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandDelegate.h48
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandDelegateImpl.h33
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m131
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandQueue.h46
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCommandQueue.m156
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConfigParser.h27
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConfigParser.m61
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConnection.h34
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVConnection.m124
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContact.h136
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContact.m1729
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContacts.h151
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVContacts.m593
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCordovaView.h23
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVCordovaView.m37
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDebug.h25
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDebugConsole.h28
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDebugConsole.m37
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDevice.h30
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVDevice.m89
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVEcho.h23
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVEcho.m44
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFile.h102
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFile.m1145
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFileTransfer.h71
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVFileTransfer.m575
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVGlobalization.h150
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVGlobalization.m790
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInAppBrowser.h72
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInAppBrowser.m494
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h57
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m112
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocalStorage.h50
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocalStorage.m521
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocation.h104
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLocation.m633
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLogger.h26
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVLogger.m38
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVNotification.h36
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVNotification.m109
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPlugin.h64
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPlugin.m148
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPluginResult.h59
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVPluginResult.m153
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVReachability.h85
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVReachability.m260
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVScreenOrientationDelegate.h28
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSound.h110
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSound.m586
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSplashScreen.h28
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVSplashScreen.m49
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVURLProtocol.h32
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVURLProtocol.m209
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVViewController.h79
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVViewController.m982
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWhitelist.h36
-rwxr-xr-xiPhone/CordovaLib/Classes/CDVWhitelist.m192
-rwxr-xr-xiPhone/CordovaLib/Classes/JSON/JSONKit.h251
-rwxr-xr-xiPhone/CordovaLib/Classes/JSON/JSONKit.m3061
-rwxr-xr-xiPhone/CordovaLib/Classes/NSArray+Comparisons.h26
-rwxr-xr-xiPhone/CordovaLib/Classes/NSArray+Comparisons.m41
-rwxr-xr-xiPhone/CordovaLib/Classes/NSData+Base64.h33
-rwxr-xr-xiPhone/CordovaLib/Classes/NSData+Base64.m286
-rwxr-xr-xiPhone/CordovaLib/Classes/NSDictionary+Extensions.h35
-rwxr-xr-xiPhone/CordovaLib/Classes/NSDictionary+Extensions.m159
-rwxr-xr-xiPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.h29
-rwxr-xr-xiPhone/CordovaLib/Classes/NSMutableArray+QueueAdditions.m58
-rwxr-xr-xiPhone/CordovaLib/Classes/UIDevice+Extensions.h31
-rwxr-xr-xiPhone/CordovaLib/Classes/UIDevice+Extensions.m47
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h30
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h46
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m29
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h32
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h23
-rwxr-xr-xiPhone/CordovaLib/Classes/compatibility/README.txt23
-rwxr-xr-xiPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj644
-rw-r--r--iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/CordovaLib.xcscheme59
-rw-r--r--iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist22
-rwxr-xr-xiPhone/CordovaLib/CordovaLib_Prefix.pch22
-rwxr-xr-xiPhone/CordovaLib/VERSION1
-rw-r--r--iPhone/FixMyStreet.xcodeproj/project.pbxproj631
-rw-r--r--iPhone/FixMyStreet/Classes/MainViewController.h18
-rw-r--r--iPhone/FixMyStreet/Classes/MainViewController.m110
-rw-r--r--iPhone/FixMyStreet/Cordova.plist65
-rw-r--r--iPhone/FixMyStreet/config.xml34
-rw-r--r--www/cordova-independent.js2
-rwxr-xr-xwww/cordova-ios-2.3.0.js6032
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