├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── Rakefile ├── android ├── AndroidManifest.xml ├── ant.properties ├── build.xml ├── project.properties ├── res │ ├── layout │ │ └── main.xml │ └── values │ │ └── strings.xml └── src │ └── com │ └── github │ └── imkira │ └── unitycountly │ └── UnityCountly.java └── unity ├── Assets ├── Build.meta ├── Build │ ├── Editor.meta │ └── Editor │ │ ├── PluginBuilder.cs │ │ └── PluginBuilder.cs.meta ├── Countly.meta ├── Countly │ ├── Demo.meta │ ├── Demo │ │ ├── UnityCountlyDemo.cs │ │ ├── UnityCountlyDemo.cs.meta │ │ ├── unity-countly-demo.unity │ │ └── unity-countly-demo.unity.meta │ ├── Editor.meta │ └── Editor │ │ ├── CountlyPostprocessor.cs │ │ ├── CountlyPostprocessor.cs.meta │ │ ├── CountlyXCodePostprocessor.py │ │ ├── CountlyXCodePostprocessor.py.meta │ │ ├── mod_pbxproj.py │ │ └── mod_pbxproj.py.meta ├── Plugins.meta └── Plugins │ ├── Countly.meta │ ├── Countly │ ├── CountlyBindings.cs │ ├── CountlyBindings.cs.meta │ ├── CountlyDatabase.cs │ ├── CountlyDatabase.cs.meta │ ├── CountlyDeviceInfo.cs │ ├── CountlyDeviceInfo.cs.meta │ ├── CountlyEvent.cs │ ├── CountlyEvent.cs.meta │ ├── CountlyManager.cs │ ├── CountlyManager.cs.meta │ ├── CountlyUtils.cs │ └── CountlyUtils.cs.meta │ ├── iOS.meta │ └── iOS │ ├── UnityCountly.mm │ └── UnityCountly.mm.meta └── ProjectSettings ├── AudioManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── InputManager.asset ├── NavMeshLayers.asset ├── NetworkManager.asset ├── ProjectSettings.asset ├── QualitySettings.asset ├── TagManager.asset └── TimeManager.asset /.gitattributes: -------------------------------------------------------------------------------- 1 | /unity/Assets/Countly/Editor/mod_pbxproj.py linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOSX 2 | .DS_Store 3 | 4 | # VIM 5 | *.swp 6 | 7 | # packages 8 | /packages 9 | 10 | # Android 11 | /android/bin/* 12 | /android/libs/* 13 | /android/gen 14 | /android/proguard.cfg 15 | /android/proguard-project.txt 16 | /android/local.properties 17 | 18 | # Unity 19 | /unity/Library 20 | /unity/Assembly-* 21 | /unity/*.csproj 22 | /unity/*.sln 23 | /unity/*.pidb 24 | /unity/*.userprefs 25 | /unity/Temp 26 | 27 | /unity/Assets/Plugins/Android/Countly.jar 28 | /unity/Assets/Plugins/Android/Countly.jar.meta 29 | /unity/Assets/Plugins/Android.meta 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | unity-countly 2 | ============= 3 | 4 | unity-countly is a [Unity 3D](http://unity3d.com) plugin for facilitating 5 | integration with [Countly](http://count.ly) Mobile Analytics. 6 | 7 | ## Why? 8 | 9 | Although there is 10 | [countly-sdk-unity](https://github.com/Countly/countly-sdk-unity), I believe it 11 | does not match the quality of the iOS and Android versions provided by 12 | [Countly Team](https://github.com/Countly). 13 | 14 | 15 | unity-countly provides the following advantages over countly-sdk-unity: 16 | 17 | - More robust (for instance if you run ```Countly.Instance.OnStart()```, 18 | ```Countly.Instance.OnStop()``` more than once you will get errors). 19 | - Countly SDK 2.0 compliant. 20 | - Understands when your app goes to background and comes to foreground. 21 | - Events that could not be delivered are stored so that they can be delivered 22 | next time your app starts. 23 | - Detects mobile carrier name. 24 | - Detects locale properly. 25 | 26 | ## Requirements 27 | 28 | * Unity 3.x Pro or above. 29 | 30 | ## Change Log 31 | 32 | The [change log](https://github.com/imkira/unity-countly/releases/) 33 | contains the list of changes and latest version information of each package. 34 | 35 | # Download Package 36 | 37 | Latest release: v0.0.3 38 | - [Download](https://github.com/imkira/unity-countly/releases/download/v0.0.3/unity-countly.unitypackage) 39 | - [Download demo](https://github.com/imkira/unity-countly/releases/download/v0.0.3/unity-countly-demo.unitypackage) 40 | 41 | ## How To Integrate 42 | 43 | * Create a ```CountlyManager``` object in your scene. 44 | * Add the ```Assets/Plugins/Countly/CountlyManager.cs``` component to it. 45 | * Set the App Key parameter. 46 | 47 | If you leave ```App Key``` blank, you can initialize Countly at any time 48 | using ```CountlyManager.Init("your_app_key_here"`);``` 49 | 50 | ## Emitting Events 51 | 52 | ``` 53 | CountlyManager.Emit("dummy_event", 1); 54 | ``` 55 | 56 | or 57 | 58 | ``` 59 | double count = 5.0; 60 | CountlyManager.Emit("tap", 1, duration); 61 | ``` 62 | 63 | or 64 | 65 | ``` 66 | CountlyManager.Emit("clear", 1, 67 | new Dictionary() 68 | { 69 | {"level", "1"}, 70 | {"difficulty", "normal"} 71 | }); 72 | ``` 73 | 74 | or 75 | 76 | ``` 77 | double price = 123.4; 78 | CountlyManager.Emit("purchase", 1, price, 79 | new Dictionary() 80 | { 81 | {"purchase_id", "product01"}, 82 | }); 83 | ``` 84 | 85 | or ultimately 86 | 87 | 88 | ``` 89 | Countly.Event e = new Countly.Event(); 90 | 91 | e.Key = "purchase"; 92 | e.Count = 1; 93 | e.Sum = 123.4; 94 | e.Segmentation = 95 | new Dictionary() 96 | { 97 | {"purchase_id", "product01"}, 98 | }); 99 | 100 | CountlyManager.Emit(e); 101 | ``` 102 | 103 | ## Contribute 104 | 105 | * Found a bug? 106 | * Want to contribute and add a new feature? 107 | 108 | Please fork this project and send me a pull request! 109 | 110 | ## License 111 | 112 | unity-countly is licensed under the MIT license: 113 | 114 | www.opensource.org/licenses/MIT 115 | 116 | ## Copyright 117 | 118 | Copyright (c) 2014 Mario Freitas. See 119 | [LICENSE.txt](http://github.com/imkira/unity-countly/blob/master/LICENSE.txt) 120 | for further details. 121 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- coding: UTF-8 -*- 3 | 4 | task :default => [:package] 5 | 6 | # main config 7 | unity = File.expand_path(ENV['UNITY'] || '/Applications/Unity/Unity.app') 8 | cur_dir = File.expand_path(File.dirname(__FILE__)) 9 | proj_path = File.join(cur_dir, 'unity') 10 | 11 | # android config 12 | android_jar_name = 'Countly.jar' 13 | android_dir = File.join(cur_dir, 'android') 14 | android_classes = File.join(unity, 'Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar') 15 | android_jar = File.join(android_dir, "bin/#{android_jar_name}") 16 | 17 | # package config 18 | package_dir = File.join(cur_dir, 'packages') 19 | package_core_dir = File.join(package_dir, 'core') 20 | package_core = File.join(package_dir, 'unity-countly.unitypackage') 21 | package_demo = File.join(package_dir, 'unity-countly-demo.unitypackage') 22 | 23 | namespace :build do 24 | directory File.join(android_dir, 'libs') 25 | 26 | file android_jar => File.join(android_dir, 'libs') do 27 | Dir.chdir(android_dir) do 28 | sh 'android update project -p .' 29 | cp_r(android_classes, 'libs') 30 | sh 'ant release' 31 | mv('bin/classes.jar', "bin/#{android_jar_name}") 32 | end 33 | end 34 | 35 | desc 'Build plugin for Android' 36 | task :android => android_jar do 37 | end 38 | 39 | desc 'Clean any builds' 40 | task :clean do 41 | Dir.chdir(android_dir) do 42 | sh 'android update project -p .' 43 | sh 'ant clean' 44 | rm_rf 'libs' 45 | end 46 | end 47 | end 48 | 49 | namespace :package do 50 | def unity_batch(unity, proj_path, method) 51 | unity_bin = File.join(unity, 'Contents', 'MacOS', 'Unity') 52 | sh %Q["#{unity_bin}" -projectPath "#{proj_path}" -batchmode -quit -executeMethod "#{method}"] 53 | end 54 | 55 | directory package_core_dir => package_dir do 56 | cp_r(proj_path, package_core_dir) 57 | mkdir_p(File.join(package_core_dir, 'Assets/Plugins/Android')) 58 | end 59 | 60 | file package_core => [package_core_dir, android_jar] do 61 | cp_r(android_jar, File.join(package_core_dir, 'Assets/Plugins/Android')) 62 | unity_batch(unity, package_core_dir, 'PluginBuilder.PackageCore') 63 | mv(File.join(package_core_dir, File.basename(package_core)), package_core) 64 | end 65 | 66 | desc 'Create plugin package' 67 | task :core => package_core do 68 | end 69 | 70 | file package_demo => [package_core_dir, android_jar] do 71 | cp_r(android_jar, File.join(package_core_dir, 'Assets/Plugins/Android')) 72 | unity_batch(unity, package_core_dir, 'PluginBuilder.PackageDemo') 73 | mv(File.join(package_core_dir, File.basename(package_demo)), package_demo) 74 | end 75 | 76 | desc 'Create demo package' 77 | task :demo => package_demo do 78 | end 79 | 80 | desc 'Clean any packages' 81 | task :clean do 82 | rm_rf package_dir 83 | end 84 | end 85 | 86 | desc 'Export plugin and compatibility packages' 87 | task :package => ['package:core', 'package:demo'] 88 | 89 | desc 'Build plugin for Android' 90 | task :build => ['build:android'] 91 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /android/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked in Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /android/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 51 | 63 | 64 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | android.library=true 11 | # Project target. 12 | target=Google Inc.:Google APIs:7 13 | -------------------------------------------------------------------------------- /android/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ACTIVITY_ENTRY_NAME 4 | 5 | -------------------------------------------------------------------------------- /android/src/com/github/imkira/unitycountly/UnityCountly.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | package com.github.imkira.unitycountly; 25 | 26 | import com.unity3d.player.UnityPlayer; 27 | import android.app.Activity; 28 | import android.content.Context; 29 | import android.content.pm.PackageManager; 30 | import android.content.pm.PackageInfo; 31 | import android.telephony.TelephonyManager; 32 | import java.util.Locale; 33 | 34 | public class UnityCountly { 35 | public static String getAppVersion() { 36 | try { 37 | Context context = getApplicationContext(); 38 | 39 | if (context == null) { 40 | return null; 41 | } 42 | 43 | String packageName = context.getPackageName(); 44 | 45 | if (packageName == null) { 46 | return null; 47 | } 48 | 49 | PackageManager packageManager = context.getPackageManager(); 50 | 51 | if (packageManager == null) { 52 | return null; 53 | } 54 | 55 | PackageInfo info = packageManager.getPackageInfo(packageName, 0); 56 | 57 | if (info == null) { 58 | return null; 59 | } 60 | 61 | return info.versionName; 62 | } 63 | catch (Exception e) { 64 | // do nothing 65 | } 66 | 67 | return null; 68 | } 69 | 70 | public static String getLocaleDescription() { 71 | Locale locale = Locale.getDefault(); 72 | 73 | if (locale == null) { 74 | return null; 75 | } 76 | 77 | return locale.getLanguage() + "_" + locale.getCountry(); 78 | } 79 | 80 | public static String getCarrierName() { 81 | try { 82 | Context context = getApplicationContext(); 83 | 84 | if (context == null) { 85 | return null; 86 | } 87 | 88 | TelephonyManager manager = 89 | (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); 90 | 91 | if (manager == null) { 92 | return null; 93 | } 94 | 95 | return manager.getNetworkOperatorName(); 96 | } 97 | catch (Exception e) { 98 | // do nothing 99 | } 100 | 101 | return null; 102 | } 103 | 104 | private static Activity getCurrentActivity() { 105 | return UnityPlayer.currentActivity; 106 | } 107 | 108 | private static Context getApplicationContext() { 109 | Activity activity = getCurrentActivity(); 110 | 111 | if (activity == null) { 112 | return null; 113 | } 114 | 115 | return activity.getApplicationContext(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /unity/Assets/Build.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0cfe2e254f744a66adde5106c6ceba9 3 | -------------------------------------------------------------------------------- /unity/Assets/Build/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 362c970e344364c1fb9badef0d4a8154 3 | -------------------------------------------------------------------------------- /unity/Assets/Build/Editor/PluginBuilder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEditor; 25 | using UnityEngine; 26 | 27 | public static class PluginBuilder 28 | { 29 | public static void PackageCore() 30 | { 31 | string[] assetPaths = new string[] 32 | { 33 | "Assets/Countly/Editor", 34 | "Assets/Plugins/Countly", 35 | "Assets/Plugins/iOS/UnityCountly.mm", 36 | "Assets/Plugins/Android/Countly.jar" 37 | }; 38 | 39 | string packagePath = "unity-countly.unitypackage"; 40 | 41 | ImportAssetOptions importOpts = ImportAssetOptions.Default; 42 | importOpts |= ImportAssetOptions.ForceSynchronousImport; 43 | importOpts |= ImportAssetOptions.ImportRecursive; 44 | AssetDatabase.Refresh(importOpts); 45 | 46 | ExportPackageOptions exportOpts = ExportPackageOptions.Recurse; 47 | AssetDatabase.ExportPackage(assetPaths, packagePath, exportOpts); 48 | } 49 | 50 | public static void PackageDemo() 51 | { 52 | string[] assetPaths = new string[] 53 | { 54 | "Assets/Countly/Demo" 55 | }; 56 | 57 | string packagePath = "unity-countly-demo.unitypackage"; 58 | 59 | ImportAssetOptions importOpts = ImportAssetOptions.Default; 60 | importOpts |= ImportAssetOptions.ForceSynchronousImport; 61 | importOpts |= ImportAssetOptions.ImportRecursive; 62 | AssetDatabase.Refresh(importOpts); 63 | 64 | ExportPackageOptions exportOpts = ExportPackageOptions.Recurse; 65 | AssetDatabase.ExportPackage(assetPaths, packagePath, exportOpts); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /unity/Assets/Build/Editor/PluginBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 831d57057ed9c47c38115d1502a1986f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Countly.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 233b92f2999dc46698fac1b65a574f75 3 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Demo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89d40fa2b8a944a529d2c1c4421e1edc 3 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Demo/UnityCountlyDemo.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | public class UnityCountlyDemo : MonoBehaviour 5 | { 6 | private void Awake() 7 | { 8 | CountlyManager.Init("put_your_app_key_here"); 9 | } 10 | 11 | public void EmitPurchase() 12 | { 13 | double price = 100; 14 | CountlyManager.Emit("purchase", 1, price, 15 | new Dictionary() 16 | { 17 | {"purchase_id", "product01"}, 18 | }); 19 | } 20 | 21 | public void EmitCrazyEvent() 22 | { 23 | CountlyManager.Emit("UTF8こんにちはWorld", 1, 10.25, 24 | new Dictionary() 25 | { 26 | {"demo1", "demo2"}, 27 | {"demo3", "Handles UTF8-テスト JSON\"\nstrings"}, 28 | {"demo4", "1"} 29 | }); 30 | } 31 | 32 | private void OnGUI() 33 | { 34 | Rect rect; 35 | 36 | rect = new Rect(10, Screen.height - 320, Screen.width - 20, 150); 37 | 38 | if (GUI.Button(rect, "Emit purchase event")) 39 | { 40 | Debug.Log("Emitting purchase event..."); 41 | 42 | EmitPurchase(); 43 | } 44 | 45 | rect = new Rect(10, Screen.height - 160, Screen.width - 20, 150); 46 | 47 | if (GUI.Button(rect, "Emit crazy event")) 48 | { 49 | Debug.Log("Emitting crazy event..."); 50 | 51 | EmitCrazyEvent(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Demo/UnityCountlyDemo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0889e25b341d412ab45248d6214ef8d 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Demo/unity-countly-demo.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | Scene: 5 | m_ObjectHideFlags: 0 6 | m_PVSData: 7 | m_QueryMode: 1 8 | m_PVSObjectsArray: [] 9 | m_PVSPortalsArray: [] 10 | m_OcclusionBakeSettings: 11 | viewCellSize: 1 12 | bakeMode: 2 13 | memoryUsage: 10485760 14 | --- !u!104 &2 15 | RenderSettings: 16 | m_Fog: 0 17 | m_FogColor: {r: .5, g: .5, b: .5, a: 1} 18 | m_FogMode: 3 19 | m_FogDensity: .00999999978 20 | m_LinearFogStart: 0 21 | m_LinearFogEnd: 300 22 | m_AmbientLight: {r: .200000003, g: .200000003, b: .200000003, a: 1} 23 | m_SkyboxMaterial: {fileID: 0} 24 | m_HaloStrength: .5 25 | m_FlareStrength: 1 26 | m_HaloTexture: {fileID: 0} 27 | m_SpotCookie: {fileID: 0} 28 | m_ObjectHideFlags: 0 29 | --- !u!127 &3 30 | GameManager: 31 | m_ObjectHideFlags: 0 32 | --- !u!157 &4 33 | LightmapSettings: 34 | m_ObjectHideFlags: 0 35 | m_LightProbes: {fileID: 0} 36 | m_Lightmaps: [] 37 | m_LightmapsMode: 1 38 | m_BakedColorSpace: 0 39 | m_UseDualLightmapsInForward: 0 40 | m_LightmapEditorSettings: 41 | m_Resolution: 50 42 | m_LastUsedResolution: 0 43 | m_TextureWidth: 1024 44 | m_TextureHeight: 1024 45 | m_BounceBoost: 1 46 | m_BounceIntensity: 1 47 | m_SkyLightColor: {r: .860000014, g: .930000007, b: 1, a: 1} 48 | m_SkyLightIntensity: 0 49 | m_Quality: 0 50 | m_Bounces: 1 51 | m_FinalGatherRays: 1000 52 | m_FinalGatherContrastThreshold: .0500000007 53 | m_FinalGatherGradientThreshold: 0 54 | m_FinalGatherInterpolationPoints: 15 55 | m_AOAmount: 0 56 | m_AOMaxDistance: .100000001 57 | m_AOContrast: 1 58 | m_LODSurfaceMappingDistance: 1 59 | m_Padding: 0 60 | m_TextureCompression: 0 61 | m_LockAtlas: 0 62 | --- !u!196 &5 63 | NavMeshSettings: 64 | m_ObjectHideFlags: 0 65 | m_BuildSettings: 66 | agentRadius: .5 67 | agentHeight: 2 68 | agentSlope: 45 69 | agentClimb: .400000006 70 | ledgeDropHeight: 0 71 | maxJumpAcrossDistance: 0 72 | accuratePlacement: 0 73 | minRegionArea: 2 74 | widthInaccuracy: 16.666666 75 | heightInaccuracy: 10 76 | m_NavMesh: {fileID: 0} 77 | --- !u!1 &455475574 78 | GameObject: 79 | m_ObjectHideFlags: 0 80 | m_PrefabParentObject: {fileID: 0} 81 | m_PrefabInternal: {fileID: 0} 82 | serializedVersion: 3 83 | m_Component: 84 | - 4: {fileID: 455475575} 85 | - 114: {fileID: 455475576} 86 | m_Layer: 0 87 | m_Name: CountlyManager 88 | m_TagString: Untagged 89 | m_Icon: {fileID: 0} 90 | m_NavMeshLayer: 0 91 | m_StaticEditorFlags: 0 92 | m_IsActive: 1 93 | --- !u!4 &455475575 94 | Transform: 95 | m_ObjectHideFlags: 0 96 | m_PrefabParentObject: {fileID: 0} 97 | m_PrefabInternal: {fileID: 0} 98 | m_GameObject: {fileID: 455475574} 99 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 100 | m_LocalPosition: {x: 0, y: 0, z: 0} 101 | m_LocalScale: {x: 1, y: 1, z: 1} 102 | m_Children: [] 103 | m_Father: {fileID: 0} 104 | --- !u!114 &455475576 105 | MonoBehaviour: 106 | m_ObjectHideFlags: 0 107 | m_PrefabParentObject: {fileID: 0} 108 | m_PrefabInternal: {fileID: 0} 109 | m_GameObject: {fileID: 455475574} 110 | m_Enabled: 1 111 | m_EditorHideFlags: 0 112 | m_Script: {fileID: 11500000, guid: 316f633e5574640dca547070fbe4ccdc, type: 1} 113 | m_Name: 114 | appHost: https://cloud.count.ly 115 | appKey: 116 | allowDebug: 1 117 | updateInterval: 60 118 | eventSendThreshold: 10 119 | queueLimit: 1024 120 | queueUsesStorage: 1 121 | --- !u!1 &1430026270 122 | GameObject: 123 | m_ObjectHideFlags: 0 124 | m_PrefabParentObject: {fileID: 0} 125 | m_PrefabInternal: {fileID: 0} 126 | serializedVersion: 3 127 | m_Component: 128 | - 4: {fileID: 1430026271} 129 | - 114: {fileID: 1430026272} 130 | m_Layer: 0 131 | m_Name: DemoButton 132 | m_TagString: Untagged 133 | m_Icon: {fileID: 0} 134 | m_NavMeshLayer: 0 135 | m_StaticEditorFlags: 0 136 | m_IsActive: 1 137 | --- !u!4 &1430026271 138 | Transform: 139 | m_ObjectHideFlags: 0 140 | m_PrefabParentObject: {fileID: 0} 141 | m_PrefabInternal: {fileID: 0} 142 | m_GameObject: {fileID: 1430026270} 143 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 144 | m_LocalPosition: {x: 0, y: 0, z: 0} 145 | m_LocalScale: {x: 1, y: 1, z: 1} 146 | m_Children: [] 147 | m_Father: {fileID: 0} 148 | --- !u!114 &1430026272 149 | MonoBehaviour: 150 | m_ObjectHideFlags: 0 151 | m_PrefabParentObject: {fileID: 0} 152 | m_PrefabInternal: {fileID: 0} 153 | m_GameObject: {fileID: 1430026270} 154 | m_Enabled: 1 155 | m_EditorHideFlags: 0 156 | m_Script: {fileID: 11500000, guid: d0889e25b341d412ab45248d6214ef8d, type: 1} 157 | m_Name: 158 | --- !u!1 &1742665743 159 | GameObject: 160 | m_ObjectHideFlags: 0 161 | m_PrefabParentObject: {fileID: 0} 162 | m_PrefabInternal: {fileID: 0} 163 | serializedVersion: 3 164 | m_Component: 165 | - 4: {fileID: 1742665744} 166 | - 20: {fileID: 1742665745} 167 | - 92: {fileID: 1742665747} 168 | - 124: {fileID: 1742665748} 169 | - 81: {fileID: 1742665746} 170 | m_Layer: 0 171 | m_Name: Camera 172 | m_TagString: MainCamera 173 | m_Icon: {fileID: 0} 174 | m_NavMeshLayer: 0 175 | m_StaticEditorFlags: 0 176 | m_IsActive: 1 177 | --- !u!4 &1742665744 178 | Transform: 179 | m_ObjectHideFlags: 0 180 | m_PrefabParentObject: {fileID: 0} 181 | m_PrefabInternal: {fileID: 0} 182 | m_GameObject: {fileID: 1742665743} 183 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 184 | m_LocalPosition: {x: 0, y: 0, z: -1} 185 | m_LocalScale: {x: 1, y: 1, z: 1} 186 | m_Children: [] 187 | m_Father: {fileID: 0} 188 | --- !u!20 &1742665745 189 | Camera: 190 | m_ObjectHideFlags: 0 191 | m_PrefabParentObject: {fileID: 0} 192 | m_PrefabInternal: {fileID: 0} 193 | m_GameObject: {fileID: 1742665743} 194 | m_Enabled: 1 195 | serializedVersion: 2 196 | m_ClearFlags: 1 197 | m_BackGroundColor: {r: .192156866, g: .301960796, b: .474509805, a: .0196078438} 198 | m_NormalizedViewPortRect: 199 | serializedVersion: 2 200 | x: 0 201 | y: 0 202 | width: 1 203 | height: 1 204 | near clip plane: .100000001 205 | far clip plane: 10 206 | field of view: 60 207 | orthographic: 1 208 | orthographic size: 100 209 | m_Depth: -1 210 | m_CullingMask: 211 | serializedVersion: 2 212 | m_Bits: 4294967295 213 | m_RenderingPath: -1 214 | m_TargetTexture: {fileID: 0} 215 | m_HDR: 0 216 | --- !u!81 &1742665746 217 | AudioListener: 218 | m_ObjectHideFlags: 0 219 | m_PrefabParentObject: {fileID: 0} 220 | m_PrefabInternal: {fileID: 0} 221 | m_GameObject: {fileID: 1742665743} 222 | m_Enabled: 1 223 | --- !u!92 &1742665747 224 | Behaviour: 225 | m_ObjectHideFlags: 0 226 | m_PrefabParentObject: {fileID: 0} 227 | m_PrefabInternal: {fileID: 0} 228 | m_GameObject: {fileID: 1742665743} 229 | m_Enabled: 1 230 | --- !u!124 &1742665748 231 | Behaviour: 232 | m_ObjectHideFlags: 0 233 | m_PrefabParentObject: {fileID: 0} 234 | m_PrefabInternal: {fileID: 0} 235 | m_GameObject: {fileID: 1742665743} 236 | m_Enabled: 1 237 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Demo/unity-countly-demo.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acc96b0ebe3454644aa65e6ae82d73c6 3 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e385509c7510c4b2a80d3f0be4cbd18b 3 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/CountlyPostprocessor.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEditor; 25 | using UnityEditor.Callbacks; 26 | using UnityEngine; 27 | using System; 28 | using System.IO; 29 | using System.Diagnostics; 30 | using Debug = UnityEngine.Debug; 31 | 32 | public class CountlyPostprocessor : Editor 33 | { 34 | [PostProcessBuild(9999)] 35 | public static void OnPostprocessBuild(BuildTarget target, 36 | string pathToBuildProject) 37 | { 38 | if (target != BuildTarget.iPhone) 39 | { 40 | return; 41 | } 42 | 43 | string script = "CountlyXCodePostprocessor.py"; 44 | string pathToXCodeProject = GetXCodeProjectPath(pathToBuildProject); 45 | string args = String.Format("\"{0}\"", pathToXCodeProject); 46 | string result; 47 | int exitCode; 48 | 49 | try 50 | { 51 | exitCode = ExecuteScript(script, args, out result); 52 | } 53 | catch (Exception e) 54 | { 55 | Debug.LogError("CountlyPostprocessor: Could not execute " + 56 | script + "! Make sure the script has executable permissions!\n" + 57 | "Exception: (" + e + ")"); 58 | return; 59 | } 60 | 61 | if ((exitCode != 0) || (string.IsNullOrEmpty(result))) 62 | { 63 | Debug.LogError("CountlyPostprocessor: Postprocess failed: " + result); 64 | return; 65 | } 66 | 67 | Debug.Log(result); 68 | } 69 | 70 | private static string GetXCodeProjectPath(string pathToBuildProject) 71 | { 72 | string pbxproj = pathToBuildProject; 73 | pbxproj = Path.Combine(pbxproj, "Unity-iPhone.xcodeproj"); 74 | pbxproj = Path.Combine(pbxproj, "project.pbxproj"); 75 | return Path.GetFullPath(pbxproj); 76 | } 77 | 78 | private static int ExecuteScript(string script, string arguments, 79 | out string output) 80 | { 81 | StackFrame sf = new StackFrame(true); 82 | StackTrace st = new StackTrace(sf); 83 | 84 | sf = st.GetFrame(0); 85 | string dir = Path.GetDirectoryName(sf.GetFileName()); 86 | string fileName = Path.Combine(dir, script); 87 | 88 | AddExecutablePermissionToScript(fileName); 89 | return Execute(fileName, arguments, out output); 90 | } 91 | 92 | private static void AddExecutablePermissionToScript(string script) 93 | { 94 | string args = String.Format("u+x \"{0}\"", script); 95 | string result; 96 | 97 | try 98 | { 99 | Execute("chmod", args, out result); 100 | } 101 | catch (Exception) 102 | { 103 | } 104 | } 105 | 106 | public static int Execute(string fileName, string arguments, 107 | out string output) 108 | { 109 | ProcessStartInfo psi = new ProcessStartInfo(); 110 | psi.UseShellExecute = false; 111 | psi.RedirectStandardError = true; 112 | psi.RedirectStandardOutput = true; 113 | psi.RedirectStandardInput = true; 114 | psi.WindowStyle = ProcessWindowStyle.Hidden; 115 | psi.CreateNoWindow = true; 116 | psi.ErrorDialog = false; 117 | psi.FileName = fileName; 118 | psi.Arguments = arguments; 119 | 120 | using (Process process = Process.Start(psi)) 121 | { 122 | process.StandardInput.Close(); 123 | StreamReader sOut = process.StandardOutput; 124 | StreamReader sErr = process.StandardError; 125 | output = sOut.ReadToEnd() + sErr.ReadToEnd(); 126 | sOut.Close(); 127 | sErr.Close(); 128 | process.WaitForExit(); 129 | return process.ExitCode; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/CountlyPostprocessor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b75e88bca9bc4706a26a9eaee332986 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/CountlyXCodePostprocessor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | sys.dont_write_bytecode = True 6 | 7 | import os 8 | from mod_pbxproj import XcodeProject 9 | 10 | if len(sys.argv) != 2: 11 | sys.exit('Syntax: ') 12 | 13 | pbxproj_path = sys.argv[1] 14 | 15 | if not os.path.exists(pbxproj_path): 16 | sys.exit('File not found: %s' % pbxproj_path) 17 | 18 | # load XCode project 19 | project = XcodeProject.Load(pbxproj_path) 20 | 21 | # add Security framework 22 | project.add_file('System/Library/Frameworks/CoreTelephony.framework', tree='SDKROOT') 23 | 24 | # save 25 | if project.modified: 26 | project.backup() 27 | project.saveFormat3_2() 28 | print('CountlyPostprocessor: Successfully updated %s' % pbxproj_path); 29 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/CountlyXCodePostprocessor.py.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a86b3dafbaa14ad2b7e831542e57f79 3 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/mod_pbxproj.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Calvin Rien 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # A pbxproj file is an OpenStep format plist 16 | # {} represents dictionary of key=value pairs delimited by ; 17 | # () represents list of values delimited by , 18 | # file starts with a comment specifying the character type 19 | # // !$*UTF8*$! 20 | 21 | # when adding a file to a project, create the PBXFileReference 22 | # add the PBXFileReference's guid to a group 23 | # create a PBXBuildFile with the PBXFileReference's guid 24 | # add the PBXBuildFile to the appropriate build phase 25 | 26 | # when adding a header search path add 27 | # HEADER_SEARCH_PATHS = "path/**"; 28 | # to each XCBuildConfiguration object 29 | 30 | # Xcode4 will read either a OpenStep or XML plist. 31 | # this script uses `plutil` to validate, read and write 32 | # the pbxproj file. Plutil is available in OS X 10.2 and higher 33 | # Plutil can't write OpenStep plists, so I save as XML 34 | 35 | import datetime 36 | import json 37 | import ntpath 38 | import os 39 | import plistlib 40 | import re 41 | import shutil 42 | import subprocess 43 | import uuid 44 | 45 | from UserDict import IterableUserDict 46 | from UserList import UserList 47 | 48 | regex = '[a-zA-Z0-9\\._/-]*' 49 | 50 | 51 | class PBXEncoder(json.JSONEncoder): 52 | 53 | def default(self, obj): 54 | """Tests the input object, obj, to encode as JSON.""" 55 | 56 | if isinstance(obj, (PBXList, PBXDict)): 57 | return obj.data 58 | 59 | return json.JSONEncoder.default(self, obj) 60 | 61 | 62 | class PBXDict(IterableUserDict): 63 | def __init__(self, d=None): 64 | if d: 65 | d = dict([(PBXType.Convert(k), PBXType.Convert(v)) for k, v in d.items()]) 66 | 67 | IterableUserDict.__init__(self, d) 68 | 69 | def __setitem__(self, key, value): 70 | IterableUserDict.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) 71 | 72 | def remove(self, key): 73 | self.data.pop(PBXType.Convert(key), None) 74 | 75 | 76 | class PBXList(UserList): 77 | def __init__(self, l=None): 78 | if isinstance(l, basestring): 79 | UserList.__init__(self) 80 | self.add(l) 81 | return 82 | elif l: 83 | l = [PBXType.Convert(v) for v in l] 84 | 85 | UserList.__init__(self, l) 86 | 87 | def add(self, value): 88 | value = PBXType.Convert(value) 89 | 90 | if value in self.data: 91 | return False 92 | 93 | self.data.append(value) 94 | return True 95 | 96 | def remove(self, value): 97 | value = PBXType.Convert(value) 98 | 99 | if value in self.data: 100 | self.data.remove(value) 101 | 102 | def __setitem__(self, key, value): 103 | UserList.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) 104 | 105 | 106 | class PBXType(PBXDict): 107 | def __init__(self, d=None): 108 | PBXDict.__init__(self, d) 109 | 110 | if 'isa' not in self: 111 | self['isa'] = self.__class__.__name__ 112 | self.id = None 113 | 114 | @staticmethod 115 | def Convert(o): 116 | if isinstance(o, list): 117 | return PBXList(o) 118 | elif isinstance(o, dict): 119 | isa = o.get('isa') 120 | 121 | if not isa: 122 | return PBXDict(o) 123 | 124 | cls = globals().get(isa) 125 | 126 | if cls and issubclass(cls, PBXType): 127 | return cls(o) 128 | 129 | print 'warning: unknown PBX type: %s' % isa 130 | return PBXDict(o) 131 | else: 132 | return o 133 | 134 | @staticmethod 135 | def IsGuid(o): 136 | return re.match('^[A-F0-9]{24}$', str(o)) 137 | 138 | @classmethod 139 | def GenerateId(cls): 140 | return ''.join(str(uuid.uuid4()).upper().split('-')[1:]) 141 | 142 | @classmethod 143 | def Create(cls, *args, **kwargs): 144 | return cls(*args, **kwargs) 145 | 146 | 147 | class PBXFileReference(PBXType): 148 | def __init__(self, d=None): 149 | PBXType.__init__(self, d) 150 | self.build_phase = None 151 | 152 | types = { 153 | '.a': ('archive.ar', 'PBXFrameworksBuildPhase'), 154 | '.app': ('wrapper.application', None), 155 | '.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'), 156 | '.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'), 157 | '.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'), 158 | '.framework': ('wrapper.framework', 'PBXFrameworksBuildPhase'), 159 | '.h': ('sourcecode.c.h', None), 160 | '.icns': ('image.icns', 'PBXResourcesBuildPhase'), 161 | '.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'), 162 | '.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'), 163 | '.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'), 164 | '.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'), 165 | '.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'), 166 | '.json': ('text.json', 'PBXResourcesBuildPhase'), 167 | '.png': ('image.png', 'PBXResourcesBuildPhase'), 168 | '.rtf': ('text.rtf', 'PBXResourcesBuildPhase'), 169 | '.tiff': ('image.tiff', 'PBXResourcesBuildPhase'), 170 | '.txt': ('text', 'PBXResourcesBuildPhase'), 171 | '.xcodeproj': ('wrapper.pb-project', None), 172 | '.xib': ('file.xib', 'PBXResourcesBuildPhase'), 173 | '.strings': ('text.plist.strings', 'PBXResourcesBuildPhase'), 174 | '.bundle': ('wrapper.plug-in', 'PBXResourcesBuildPhase'), 175 | '.dylib': ('compiled.mach-o.dylib', 'PBXFrameworksBuildPhase') 176 | } 177 | 178 | trees = [ 179 | '', 180 | '', 181 | 'BUILT_PRODUCTS_DIR', 182 | 'DEVELOPER_DIR', 183 | 'SDKROOT', 184 | 'SOURCE_ROOT', 185 | ] 186 | 187 | def guess_file_type(self, ignore_unknown_type=False): 188 | self.remove('explicitFileType') 189 | self.remove('lastKnownFileType') 190 | 191 | ext = os.path.splitext(self.get('name', ''))[1] 192 | if os.path.isdir(self.get('path')) and ext != '.framework': 193 | f_type = 'folder' 194 | build_phase = None 195 | ext = '' 196 | else: 197 | f_type, build_phase = PBXFileReference.types.get(ext, ('?', 'PBXResourcesBuildPhase')) 198 | 199 | self['lastKnownFileType'] = f_type 200 | self.build_phase = build_phase 201 | 202 | if f_type == '?' and not ignore_unknown_type: 203 | print 'unknown file extension: %s' % ext 204 | print 'please add extension and Xcode type to PBXFileReference.types' 205 | 206 | return f_type 207 | 208 | def set_file_type(self, ft): 209 | self.remove('explicitFileType') 210 | self.remove('lastKnownFileType') 211 | 212 | self['explicitFileType'] = ft 213 | 214 | @classmethod 215 | def Create(cls, os_path, tree='SOURCE_ROOT', ignore_unknown_type=False): 216 | if tree not in cls.trees: 217 | print 'Not a valid sourceTree type: %s' % tree 218 | return None 219 | 220 | fr = cls() 221 | fr.id = cls.GenerateId() 222 | fr['path'] = os_path 223 | fr['name'] = os.path.split(os_path)[1] 224 | fr['sourceTree'] = '' if os.path.isabs(os_path) else tree 225 | fr.guess_file_type(ignore_unknown_type=ignore_unknown_type) 226 | 227 | return fr 228 | 229 | 230 | class PBXBuildFile(PBXType): 231 | def set_weak_link(self, weak=False): 232 | k_settings = 'settings' 233 | k_attributes = 'ATTRIBUTES' 234 | 235 | s = self.get(k_settings) 236 | 237 | if not s: 238 | if weak: 239 | self[k_settings] = PBXDict({k_attributes: PBXList(['Weak'])}) 240 | 241 | return True 242 | 243 | atr = s.get(k_attributes) 244 | 245 | if not atr: 246 | if weak: 247 | atr = PBXList() 248 | else: 249 | return False 250 | 251 | if weak: 252 | atr.add('Weak') 253 | else: 254 | atr.remove('Weak') 255 | 256 | self[k_settings][k_attributes] = atr 257 | 258 | return True 259 | 260 | def add_compiler_flag(self, flag): 261 | k_settings = 'settings' 262 | k_attributes = 'COMPILER_FLAGS' 263 | 264 | if k_settings not in self: 265 | self[k_settings] = PBXDict() 266 | 267 | if k_attributes not in self[k_settings]: 268 | self[k_settings][k_attributes] = flag 269 | return True 270 | 271 | flags = self[k_settings][k_attributes].split(' ') 272 | 273 | if flag in flags: 274 | return False 275 | 276 | flags.append(flag) 277 | 278 | self[k_settings][k_attributes] = ' '.join(flags) 279 | 280 | @classmethod 281 | def Create(cls, file_ref, weak=False): 282 | if isinstance(file_ref, PBXFileReference): 283 | file_ref = file_ref.id 284 | 285 | bf = cls() 286 | bf.id = cls.GenerateId() 287 | bf['fileRef'] = file_ref 288 | 289 | if weak: 290 | bf.set_weak_link(True) 291 | 292 | return bf 293 | 294 | 295 | class PBXGroup(PBXType): 296 | def add_child(self, ref): 297 | if not isinstance(ref, PBXDict): 298 | return None 299 | 300 | isa = ref.get('isa') 301 | 302 | if isa != 'PBXFileReference' and isa != 'PBXGroup': 303 | return None 304 | 305 | if 'children' not in self: 306 | self['children'] = PBXList() 307 | 308 | self['children'].add(ref.id) 309 | 310 | return ref.id 311 | 312 | def remove_child(self, id): 313 | if 'children' not in self: 314 | self['children'] = PBXList() 315 | return 316 | 317 | if not PBXType.IsGuid(id): 318 | id = id.id 319 | 320 | self['children'].remove(id) 321 | 322 | def has_child(self, id): 323 | if 'children' not in self: 324 | self['children'] = PBXList() 325 | return False 326 | 327 | if not PBXType.IsGuid(id): 328 | id = id.id 329 | 330 | return id in self['children'] 331 | 332 | def get_name(self): 333 | path_name = os.path.split(self.get('path', ''))[1] 334 | return self.get('name', path_name) 335 | 336 | @classmethod 337 | def Create(cls, name, path=None, tree='SOURCE_ROOT'): 338 | grp = cls() 339 | grp.id = cls.GenerateId() 340 | grp['name'] = name 341 | grp['children'] = PBXList() 342 | 343 | if path: 344 | grp['path'] = path 345 | grp['sourceTree'] = tree 346 | else: 347 | grp['sourceTree'] = '' 348 | 349 | return grp 350 | 351 | 352 | class PBXNativeTarget(PBXType): 353 | pass 354 | 355 | 356 | class PBXProject(PBXType): 357 | pass 358 | 359 | 360 | class PBXContainerItemProxy(PBXType): 361 | pass 362 | 363 | 364 | class PBXReferenceProxy(PBXType): 365 | pass 366 | 367 | 368 | class PBXVariantGroup(PBXType): 369 | pass 370 | 371 | 372 | class PBXTargetDependency(PBXType): 373 | pass 374 | 375 | 376 | class PBXBuildPhase(PBXType): 377 | def add_build_file(self, bf): 378 | if bf.get('isa') != 'PBXBuildFile': 379 | return False 380 | 381 | if 'files' not in self: 382 | self['files'] = PBXList() 383 | 384 | self['files'].add(bf.id) 385 | 386 | return True 387 | 388 | def remove_build_file(self, id): 389 | if 'files' not in self: 390 | self['files'] = PBXList() 391 | return 392 | 393 | self['files'].remove(id) 394 | 395 | def has_build_file(self, id): 396 | if 'files' not in self: 397 | self['files'] = PBXList() 398 | return False 399 | 400 | if not PBXType.IsGuid(id): 401 | id = id.id 402 | 403 | return id in self['files'] 404 | 405 | 406 | class PBXFrameworksBuildPhase(PBXBuildPhase): 407 | pass 408 | 409 | 410 | class PBXResourcesBuildPhase(PBXBuildPhase): 411 | pass 412 | 413 | 414 | class PBXShellScriptBuildPhase(PBXBuildPhase): 415 | pass 416 | 417 | 418 | class PBXSourcesBuildPhase(PBXBuildPhase): 419 | pass 420 | 421 | 422 | class PBXCopyFilesBuildPhase(PBXBuildPhase): 423 | pass 424 | 425 | 426 | class XCBuildConfiguration(PBXType): 427 | def add_search_paths(self, paths, base, key, recursive=True, escape=True): 428 | modified = False 429 | 430 | if not isinstance(paths, list): 431 | paths = [paths] 432 | 433 | if base not in self: 434 | self[base] = PBXDict() 435 | 436 | for path in paths: 437 | if recursive and not path.endswith('/**'): 438 | path = os.path.join(path, '**') 439 | 440 | if key not in self[base]: 441 | self[base][key] = PBXList() 442 | elif isinstance(self[base][key], basestring): 443 | self[base][key] = PBXList(self[base][key]) 444 | 445 | if escape: 446 | if self[base][key].add('"%s"' % path): # '\\"%s\\"' % path 447 | modified = True 448 | else: 449 | if self[base][key].add(path): # '\\"%s\\"' % path 450 | modified = True 451 | 452 | return modified 453 | 454 | def add_header_search_paths(self, paths, recursive=True): 455 | return self.add_search_paths(paths, 'buildSettings', 'HEADER_SEARCH_PATHS', recursive=recursive) 456 | 457 | def add_library_search_paths(self, paths, recursive=True): 458 | return self.add_search_paths(paths, 'buildSettings', 'LIBRARY_SEARCH_PATHS', recursive=recursive) 459 | 460 | def add_framework_search_paths(self, paths, recursive=True): 461 | return self.add_search_paths(paths, 'buildSettings', 'FRAMEWORK_SEARCH_PATHS', recursive=recursive) 462 | 463 | def add_other_cflags(self, flags): 464 | modified = False 465 | 466 | base = 'buildSettings' 467 | key = 'OTHER_CFLAGS' 468 | 469 | if isinstance(flags, basestring): 470 | flags = PBXList(flags) 471 | 472 | if base not in self: 473 | self[base] = PBXDict() 474 | 475 | for flag in flags: 476 | if key not in self[base]: 477 | self[base][key] = PBXList() 478 | elif isinstance(self[base][key], basestring): 479 | self[base][key] = PBXList(self[base][key]) 480 | 481 | if self[base][key].add(flag): 482 | self[base][key] = [e for e in self[base][key] if e] 483 | modified = True 484 | 485 | return modified 486 | 487 | def add_other_ldflags(self, flags): 488 | modified = False 489 | 490 | base = 'buildSettings' 491 | key = 'OTHER_LDFLAGS' 492 | 493 | if isinstance(flags, basestring): 494 | flags = PBXList(flags) 495 | 496 | if base not in self: 497 | self[base] = PBXDict() 498 | 499 | for flag in flags: 500 | if key not in self[base]: 501 | self[base][key] = PBXList() 502 | elif isinstance(self[base][key], basestring): 503 | self[base][key] = PBXList(self[base][key]) 504 | 505 | if self[base][key].add(flag): 506 | self[base][key] = [e for e in self[base][key] if e] 507 | modified = True 508 | 509 | return modified 510 | 511 | def remove_other_ldflags(self, flags): 512 | modified = False 513 | 514 | base = 'buildSettings' 515 | key = 'OTHER_LDFLAGS' 516 | 517 | if isinstance(flags, basestring): 518 | flags = PBXList(flags) 519 | 520 | if base in self: # there are flags, so we can "remove" something 521 | for flag in flags: 522 | if key not in self[base]: 523 | return False 524 | elif isinstance(self[base][key], basestring): 525 | self[base][key] = PBXList(self[base][key]) 526 | 527 | if self[base][key].remove(flag): 528 | self[base][key] = [e for e in self[base][key] if e] 529 | modified = True 530 | 531 | return modified 532 | 533 | 534 | class XCConfigurationList(PBXType): 535 | pass 536 | 537 | 538 | class XcodeProject(PBXDict): 539 | plutil_path = 'plutil' 540 | special_folders = ['.bundle', '.framework', '.xcodeproj'] 541 | 542 | def __init__(self, d=None, path=None): 543 | if not path: 544 | path = os.path.join(os.getcwd(), 'project.pbxproj') 545 | 546 | self.pbxproj_path = os.path.abspath(path) 547 | self.source_root = os.path.abspath(os.path.join(os.path.split(path)[0], '..')) 548 | 549 | IterableUserDict.__init__(self, d) 550 | 551 | self.data = PBXDict(self.data) 552 | self.objects = self.get('objects') 553 | self.modified = False 554 | 555 | root_id = self.get('rootObject') 556 | 557 | if root_id: 558 | self.root_object = self.objects[root_id] 559 | root_group_id = self.root_object.get('mainGroup') 560 | self.root_group = self.objects[root_group_id] 561 | else: 562 | print "error: project has no root object" 563 | self.root_object = None 564 | self.root_group = None 565 | 566 | for k, v in self.objects.iteritems(): 567 | v.id = k 568 | 569 | def add_other_cflags(self, flags): 570 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 571 | 572 | for b in build_configs: 573 | if b.add_other_cflags(flags): 574 | self.modified = True 575 | 576 | def add_other_ldflags(self, flags): 577 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 578 | 579 | for b in build_configs: 580 | if b.add_other_ldflags(flags): 581 | self.modified = True 582 | 583 | def remove_other_ldflags(self, flags): 584 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 585 | 586 | for b in build_configs: 587 | if b.remove_other_ldflags(flags): 588 | self.modified = True 589 | 590 | def add_header_search_paths(self, paths, recursive=True): 591 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 592 | 593 | for b in build_configs: 594 | if b.add_header_search_paths(paths, recursive): 595 | self.modified = True 596 | 597 | def add_framework_search_paths(self, paths, recursive=True): 598 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 599 | 600 | for b in build_configs: 601 | if b.add_framework_search_paths(paths, recursive): 602 | self.modified = True 603 | 604 | def add_library_search_paths(self, paths, recursive=True): 605 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 606 | 607 | for b in build_configs: 608 | if b.add_library_search_paths(paths, recursive): 609 | self.modified = True 610 | 611 | # TODO: need to return value if project has been modified 612 | 613 | def get_obj(self, id): 614 | return self.objects.get(id) 615 | 616 | def get_ids(self): 617 | return self.objects.keys() 618 | 619 | def get_files_by_os_path(self, os_path, tree='SOURCE_ROOT'): 620 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 621 | and f.get('path') == os_path 622 | and f.get('sourceTree') == tree] 623 | 624 | return files 625 | 626 | def get_files_by_name(self, name, parent=None): 627 | if parent: 628 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 629 | and f.get('name') == name 630 | and parent.has_child(f)] 631 | else: 632 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 633 | and f.get('name') == name] 634 | 635 | return files 636 | 637 | def get_build_files(self, id): 638 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile' 639 | and f.get('fileRef') == id] 640 | 641 | return files 642 | 643 | def get_groups_by_name(self, name, parent=None): 644 | if parent: 645 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 646 | and g.get_name() == name 647 | and parent.has_child(g)] 648 | else: 649 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 650 | and g.get_name() == name] 651 | 652 | return groups 653 | 654 | def get_or_create_group(self, name, path=None, parent=None): 655 | if not name: 656 | return None 657 | 658 | if not parent: 659 | parent = self.root_group 660 | elif not isinstance(parent, PBXGroup): 661 | # assume it's an id 662 | parent = self.objects.get(parent, self.root_group) 663 | 664 | groups = self.get_groups_by_name(name) 665 | 666 | for grp in groups: 667 | if parent.has_child(grp.id): 668 | return grp 669 | 670 | grp = PBXGroup.Create(name, path) 671 | parent.add_child(grp) 672 | 673 | self.objects[grp.id] = grp 674 | 675 | self.modified = True 676 | 677 | return grp 678 | 679 | def get_groups_by_os_path(self, path): 680 | path = os.path.abspath(path) 681 | 682 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 683 | and os.path.abspath(g.get('path', '/dev/null')) == path] 684 | 685 | return groups 686 | 687 | def get_build_phases(self, phase_name): 688 | phases = [p for p in self.objects.values() if p.get('isa') == phase_name] 689 | 690 | return phases 691 | 692 | def get_relative_path(self, os_path): 693 | return os.path.relpath(os_path, self.source_root) 694 | 695 | def verify_files(self, file_list, parent=None): 696 | # returns list of files not in the current project. 697 | if not file_list: 698 | return [] 699 | 700 | if parent: 701 | exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list and parent.has_child(f)] 702 | else: 703 | exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list] 704 | 705 | return set(file_list).difference(exists_list) 706 | 707 | def add_folder(self, os_path, parent=None, excludes=None, recursive=True, create_build_files=True): 708 | if not os.path.isdir(os_path): 709 | return [] 710 | 711 | if not excludes: 712 | excludes = [] 713 | 714 | results = [] 715 | 716 | if not parent: 717 | parent = self.root_group 718 | elif not isinstance(parent, PBXGroup): 719 | # assume it's an id 720 | parent = self.objects.get(parent, self.root_group) 721 | 722 | path_dict = {os.path.split(os_path)[0]: parent} 723 | special_list = [] 724 | 725 | for (grp_path, subdirs, files) in os.walk(os_path): 726 | parent_folder, folder_name = os.path.split(grp_path) 727 | parent = path_dict.get(parent_folder, parent) 728 | 729 | if [sp for sp in special_list if parent_folder.startswith(sp)]: 730 | continue 731 | 732 | if folder_name.startswith('.'): 733 | special_list.append(grp_path) 734 | continue 735 | 736 | if os.path.splitext(grp_path)[1] in XcodeProject.special_folders: 737 | # if this file has a special extension (bundle or framework mainly) treat it as a file 738 | special_list.append(grp_path) 739 | new_files = self.verify_files([folder_name], parent=parent) 740 | 741 | if new_files: 742 | results.extend(self.add_file(grp_path, parent, create_build_files=create_build_files)) 743 | 744 | continue 745 | 746 | # create group 747 | grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path), parent=parent) 748 | path_dict[grp_path] = grp 749 | 750 | results.append(grp) 751 | 752 | file_dict = {} 753 | 754 | for f in files: 755 | if f[0] == '.' or [m for m in excludes if re.match(m, f)]: 756 | continue 757 | 758 | kwds = { 759 | 'create_build_files': create_build_files, 760 | 'parent': grp, 761 | 'name': f 762 | } 763 | 764 | f_path = os.path.join(grp_path, f) 765 | 766 | file_dict[f_path] = kwds 767 | 768 | new_files = self.verify_files([n.get('name') for n in file_dict.values()], parent=grp) 769 | 770 | add_files = [(k, v) for k, v in file_dict.items() if v.get('name') in new_files] 771 | 772 | for path, kwds in add_files: 773 | kwds.pop('name', None) 774 | 775 | self.add_file(path, **kwds) 776 | 777 | if not recursive: 778 | break 779 | 780 | for r in results: 781 | self.objects[r.id] = r 782 | 783 | return results 784 | 785 | def path_leaf(self, path): 786 | head, tail = ntpath.split(path) 787 | return tail or ntpath.basename(head) 788 | 789 | def add_file_if_doesnt_exist(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False): 790 | for obj in self.objects.values(): 791 | if 'path' in obj: 792 | if self.path_leaf(f_path) == self.path_leaf(obj.get('path')): 793 | return [] 794 | 795 | return self.add_file(f_path, parent, tree, create_build_files, weak, ignore_unknown_type=ignore_unknown_type) 796 | 797 | def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False): 798 | results = [] 799 | abs_path = '' 800 | 801 | if os.path.isabs(f_path): 802 | abs_path = f_path 803 | 804 | if not os.path.exists(f_path): 805 | return results 806 | elif tree == 'SOURCE_ROOT': 807 | f_path = os.path.relpath(f_path, self.source_root) 808 | else: 809 | tree = '' 810 | 811 | if not parent: 812 | parent = self.root_group 813 | elif not isinstance(parent, PBXGroup): 814 | # assume it's an id 815 | parent = self.objects.get(parent, self.root_group) 816 | 817 | file_ref = PBXFileReference.Create(f_path, tree, ignore_unknown_type=ignore_unknown_type) 818 | parent.add_child(file_ref) 819 | results.append(file_ref) 820 | 821 | # create a build file for the file ref 822 | if file_ref.build_phase and create_build_files: 823 | phases = self.get_build_phases(file_ref.build_phase) 824 | 825 | for phase in phases: 826 | build_file = PBXBuildFile.Create(file_ref, weak=weak) 827 | 828 | phase.add_build_file(build_file) 829 | results.append(build_file) 830 | 831 | if abs_path and tree == 'SOURCE_ROOT' \ 832 | and os.path.isfile(abs_path) \ 833 | and file_ref.build_phase == 'PBXFrameworksBuildPhase': 834 | library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0]) 835 | self.add_library_search_paths([library_path], recursive=False) 836 | 837 | if abs_path and tree == 'SOURCE_ROOT' \ 838 | and not os.path.isfile(abs_path) \ 839 | and file_ref.build_phase == 'PBXFrameworksBuildPhase': 840 | framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0]) 841 | self.add_framework_search_paths([framework_path, '$(inherited)'], recursive=False) 842 | 843 | for r in results: 844 | self.objects[r.id] = r 845 | 846 | if results: 847 | self.modified = True 848 | 849 | return results 850 | 851 | def check_and_repair_framework(self, base): 852 | name = os.path.basename(base) 853 | 854 | if ".framework" in name: 855 | basename = name[:-len(".framework")] 856 | 857 | finalHeaders = os.path.join(base, "Headers") 858 | finalCurrent = os.path.join(base, "Versions/Current") 859 | finalLib = os.path.join(base, basename) 860 | srcHeaders = "Versions/A/Headers" 861 | srcCurrent = "A" 862 | srcLib = "Versions/A/" + basename 863 | 864 | if not os.path.exists(finalHeaders): 865 | os.symlink(srcHeaders, finalHeaders) 866 | if not os.path.exists(finalCurrent): 867 | os.symlink(srcCurrent, finalCurrent) 868 | if not os.path.exists(finalLib): 869 | os.symlink(srcLib, finalLib) 870 | 871 | def remove_group(self, grp): 872 | pass 873 | 874 | def remove_file(self, id, recursive=True): 875 | if not PBXType.IsGuid(id): 876 | id = id.id 877 | 878 | if id in self.objects: 879 | self.objects.remove(id) 880 | 881 | if recursive: 882 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'] 883 | 884 | for group in groups: 885 | if id in group['children']: 886 | group.remove_child(id) 887 | 888 | self.modified = True 889 | 890 | def move_file(self, id, dest_grp=None): 891 | pass 892 | 893 | def apply_patch(self, patch_path, xcode_path): 894 | if not os.path.isfile(patch_path) or not os.path.isdir(xcode_path): 895 | print 'ERROR: couldn\'t apply "%s" to "%s"' % (patch_path, xcode_path) 896 | return 897 | 898 | print 'applying "%s" to "%s"' % (patch_path, xcode_path) 899 | 900 | return subprocess.call(['patch', '-p1', '--forward', '--directory=%s' % xcode_path, '--input=%s' % patch_path]) 901 | 902 | def apply_mods(self, mod_dict, default_path=None): 903 | if not default_path: 904 | default_path = os.getcwd() 905 | 906 | keys = mod_dict.keys() 907 | 908 | for k in keys: 909 | v = mod_dict.pop(k) 910 | mod_dict[k.lower()] = v 911 | 912 | parent = mod_dict.pop('group', None) 913 | 914 | if parent: 915 | parent = self.get_or_create_group(parent) 916 | 917 | excludes = mod_dict.pop('excludes', []) 918 | 919 | if excludes: 920 | excludes = [re.compile(e) for e in excludes] 921 | 922 | compiler_flags = mod_dict.pop('compiler_flags', {}) 923 | 924 | for k, v in mod_dict.items(): 925 | if k == 'patches': 926 | for p in v: 927 | if not os.path.isabs(p): 928 | p = os.path.join(default_path, p) 929 | 930 | self.apply_patch(p, self.source_root) 931 | elif k == 'folders': 932 | # get and compile excludes list 933 | # do each folder individually 934 | for folder in v: 935 | kwds = {} 936 | 937 | # if path contains ':' remove it and set recursive to False 938 | if ':' in folder: 939 | args = folder.split(':') 940 | kwds['recursive'] = False 941 | folder = args.pop(0) 942 | 943 | if os.path.isabs(folder) and os.path.isdir(folder): 944 | pass 945 | else: 946 | folder = os.path.join(default_path, folder) 947 | if not os.path.isdir(folder): 948 | continue 949 | 950 | if parent: 951 | kwds['parent'] = parent 952 | 953 | if excludes: 954 | kwds['excludes'] = excludes 955 | 956 | self.add_folder(folder, **kwds) 957 | elif k == 'headerpaths' or k == 'librarypaths': 958 | paths = [] 959 | 960 | for p in v: 961 | if p.endswith('/**'): 962 | p = os.path.split(p)[0] 963 | 964 | if not os.path.isabs(p): 965 | p = os.path.join(default_path, p) 966 | 967 | if not os.path.exists(p): 968 | continue 969 | 970 | p = self.get_relative_path(p) 971 | paths.append(os.path.join('$(SRCROOT)', p, "**")) 972 | 973 | if k == 'headerpaths': 974 | self.add_header_search_paths(paths) 975 | else: 976 | self.add_library_search_paths(paths) 977 | elif k == 'other_cflags': 978 | self.add_other_cflags(v) 979 | elif k == 'other_ldflags': 980 | self.add_other_ldflags(v) 981 | elif k == 'libs' or k == 'frameworks' or k == 'files': 982 | paths = {} 983 | 984 | for p in v: 985 | kwds = {} 986 | 987 | if ':' in p: 988 | args = p.split(':') 989 | p = args.pop(0) 990 | 991 | if 'weak' in args: 992 | kwds['weak'] = True 993 | 994 | file_path = os.path.join(default_path, p) 995 | search_path, file_name = os.path.split(file_path) 996 | 997 | if [m for m in excludes if re.match(m, file_name)]: 998 | continue 999 | 1000 | try: 1001 | expr = re.compile(file_name) 1002 | except re.error: 1003 | expr = None 1004 | 1005 | if expr and os.path.isdir(search_path): 1006 | file_list = os.listdir(search_path) 1007 | 1008 | for f in file_list: 1009 | if [m for m in excludes if re.match(m, f)]: 1010 | continue 1011 | 1012 | if re.search(expr, f): 1013 | kwds['name'] = f 1014 | paths[os.path.join(search_path, f)] = kwds 1015 | p = None 1016 | 1017 | if k == 'libs': 1018 | kwds['parent'] = self.get_or_create_group('Libraries', parent=parent) 1019 | elif k == 'frameworks': 1020 | kwds['parent'] = self.get_or_create_group('Frameworks', parent=parent) 1021 | 1022 | if p: 1023 | kwds['name'] = file_name 1024 | 1025 | if k == 'libs': 1026 | p = os.path.join('usr', 'lib', p) 1027 | kwds['tree'] = 'SDKROOT' 1028 | elif k == 'frameworks': 1029 | p = os.path.join('System', 'Library', 'Frameworks', p) 1030 | kwds['tree'] = 'SDKROOT' 1031 | elif k == 'files' and not os.path.exists(file_path): 1032 | # don't add non-existent files to the project. 1033 | continue 1034 | 1035 | paths[p] = kwds 1036 | 1037 | new_files = self.verify_files([n.get('name') for n in paths.values()]) 1038 | add_files = [(k, v) for k, v in paths.items() if v.get('name') in new_files] 1039 | 1040 | for path, kwds in add_files: 1041 | kwds.pop('name', None) 1042 | 1043 | if 'parent' not in kwds and parent: 1044 | kwds['parent'] = parent 1045 | 1046 | self.add_file(path, **kwds) 1047 | 1048 | if compiler_flags: 1049 | for k, v in compiler_flags.items(): 1050 | filerefs = [] 1051 | 1052 | for f in v: 1053 | filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference' 1054 | and fr.get('name') == f]) 1055 | 1056 | buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile' 1057 | and bf.get('fileRef') in filerefs] 1058 | 1059 | for bf in buildfiles: 1060 | if bf.add_compiler_flag(k): 1061 | self.modified = True 1062 | 1063 | def backup(self, file_name=None, backup_name=None): 1064 | if not file_name: 1065 | file_name = self.pbxproj_path 1066 | 1067 | if not backup_name: 1068 | backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S')) 1069 | 1070 | shutil.copy2(file_name, backup_name) 1071 | 1072 | def save(self, file_name=None): 1073 | """Saves in old (xml) format""" 1074 | if not file_name: 1075 | file_name = self.pbxproj_path 1076 | 1077 | # This code is adapted from plistlib.writePlist 1078 | with open(file_name, "w") as f: 1079 | writer = PBXWriter(f) 1080 | writer.writeln("") 1081 | writer.writeValue(self.data) 1082 | writer.writeln("") 1083 | 1084 | def saveFormat3_2(self, file_name=None): 1085 | """Save in Xcode 3.2 compatible (new) format""" 1086 | if not file_name: 1087 | file_name = self.pbxproj_path 1088 | 1089 | # process to get the section's info and names 1090 | objs = self.data.get('objects') 1091 | sections = dict() 1092 | uuids = dict() 1093 | 1094 | for key in objs: 1095 | l = list() 1096 | 1097 | if objs.get(key).get('isa') in sections: 1098 | l = sections.get(objs.get(key).get('isa')) 1099 | 1100 | l.append(tuple([key, objs.get(key)])) 1101 | sections[objs.get(key).get('isa')] = l 1102 | 1103 | if 'name' in objs.get(key): 1104 | uuids[key] = objs.get(key).get('name') 1105 | elif 'path' in objs.get(key): 1106 | uuids[key] = objs.get(key).get('path') 1107 | else: 1108 | if objs.get(key).get('isa') == 'PBXProject': 1109 | uuids[objs.get(key).get('buildConfigurationList')] = 'Build configuration list for PBXProject "Unity-iPhone"' 1110 | elif objs.get(key).get('isa')[0:3] == 'PBX': 1111 | uuids[key] = objs.get(key).get('isa')[3:-10] 1112 | else: 1113 | uuids[key] = 'Build configuration list for PBXNativeTarget "TARGET_NAME"' 1114 | 1115 | ro = self.data.get('rootObject') 1116 | uuids[ro] = 'Project Object' 1117 | 1118 | for key in objs: 1119 | # transitive references (used in the BuildFile section) 1120 | if 'fileRef' in objs.get(key) and objs.get(key).get('fileRef') in uuids: 1121 | uuids[key] = uuids[objs.get(key).get('fileRef')] 1122 | 1123 | # transitive reference to the target name (used in the Native target section) 1124 | if objs.get(key).get('isa') == 'PBXNativeTarget': 1125 | uuids[objs.get(key).get('buildConfigurationList')] = uuids[objs.get(key).get('buildConfigurationList')].replace('TARGET_NAME', uuids[key]) 1126 | 1127 | self.uuids = uuids 1128 | self.sections = sections 1129 | 1130 | out = open(file_name, 'w') 1131 | out.write('// !$*UTF8*$!\n') 1132 | self._printNewXCodeFormat(out, self.data, '', enters=True) 1133 | out.close() 1134 | 1135 | @classmethod 1136 | def addslashes(cls, s): 1137 | d = {'"': '\\"', "'": "\\'", "\0": "\\\0", "\\": "\\\\"} 1138 | return ''.join(d.get(c, c) for c in s) 1139 | 1140 | def _printNewXCodeFormat(self, out, root, deep, enters=True): 1141 | if isinstance(root, IterableUserDict): 1142 | out.write('{') 1143 | 1144 | if enters: 1145 | out.write('\n') 1146 | 1147 | isa = root.pop('isa', '') 1148 | 1149 | if isa != '': # keep the isa in the first spot 1150 | if enters: 1151 | out.write('\t' + deep) 1152 | 1153 | out.write('isa = ') 1154 | self._printNewXCodeFormat(out, isa, '\t' + deep, enters=enters) 1155 | out.write(';') 1156 | 1157 | if enters: 1158 | out.write('\n') 1159 | else: 1160 | out.write(' ') 1161 | 1162 | for key in sorted(root.iterkeys()): # keep the same order as Apple. 1163 | if enters: 1164 | out.write('\t' + deep) 1165 | 1166 | if re.match(regex, key).group(0) == key: 1167 | out.write(key.encode("utf-8") + ' = ') 1168 | else: 1169 | out.write('"' + key.encode("utf-8") + '" = ') 1170 | 1171 | if key == 'objects': 1172 | out.write('{') # open the objects section 1173 | 1174 | if enters: 1175 | out.write('\n') 1176 | #root.remove('objects') # remove it to avoid problems 1177 | 1178 | sections = [ 1179 | ('PBXBuildFile', False), 1180 | ('PBXCopyFilesBuildPhase', True), 1181 | ('PBXFileReference', False), 1182 | ('PBXFrameworksBuildPhase', True), 1183 | ('PBXGroup', True), 1184 | ('PBXNativeTarget', True), 1185 | ('PBXProject', True), 1186 | ('PBXResourcesBuildPhase', True), 1187 | ('PBXShellScriptBuildPhase', True), 1188 | ('PBXSourcesBuildPhase', True), 1189 | ('XCBuildConfiguration', True), 1190 | ('XCConfigurationList', True), 1191 | ('PBXTargetDependency', True), 1192 | ('PBXVariantGroup', True), 1193 | ('PBXReferenceProxy', True), 1194 | ('PBXContainerItemProxy', True)] 1195 | 1196 | for section in sections: # iterate over the sections 1197 | if self.sections.get(section[0]) is None: 1198 | continue 1199 | 1200 | out.write('\n/* Begin %s section */' % section[0].encode("utf-8")) 1201 | self.sections.get(section[0]).sort(cmp=lambda x, y: cmp(x[0], y[0])) 1202 | 1203 | for pair in self.sections.get(section[0]): 1204 | key = pair[0] 1205 | value = pair[1] 1206 | out.write('\n') 1207 | 1208 | if enters: 1209 | out.write('\t\t' + deep) 1210 | 1211 | out.write(key.encode("utf-8")) 1212 | 1213 | if key in self.uuids: 1214 | out.write(" /* " + self.uuids[key].encode("utf-8") + " */") 1215 | 1216 | out.write(" = ") 1217 | self._printNewXCodeFormat(out, value, '\t\t' + deep, enters=section[1]) 1218 | out.write(';') 1219 | 1220 | out.write('\n/* End %s section */\n' % section[0].encode("utf-8")) 1221 | 1222 | out.write(deep + '\t}') # close of the objects section 1223 | else: 1224 | self._printNewXCodeFormat(out, root[key], '\t' + deep, enters=enters) 1225 | 1226 | out.write(';') 1227 | 1228 | if enters: 1229 | out.write('\n') 1230 | else: 1231 | out.write(' ') 1232 | 1233 | root['isa'] = isa # restore the isa for further calls 1234 | 1235 | if enters: 1236 | out.write(deep) 1237 | 1238 | out.write('}') 1239 | 1240 | elif isinstance(root, UserList): 1241 | out.write('(') 1242 | 1243 | if enters: 1244 | out.write('\n') 1245 | 1246 | for value in root: 1247 | if enters: 1248 | out.write('\t' + deep) 1249 | 1250 | self._printNewXCodeFormat(out, value, '\t' + deep, enters=enters) 1251 | out.write(',') 1252 | 1253 | if enters: 1254 | out.write('\n') 1255 | 1256 | if enters: 1257 | out.write(deep) 1258 | 1259 | out.write(')') 1260 | 1261 | else: 1262 | if len(root) > 0 and re.match(regex, root).group(0) == root: 1263 | out.write(root.encode("utf-8")) 1264 | else: 1265 | out.write('"' + XcodeProject.addslashes(root.encode("utf-8")) + '"') 1266 | 1267 | if root in self.uuids: 1268 | out.write(" /* " + self.uuids[root].encode("utf-8") + " */") 1269 | 1270 | @classmethod 1271 | def Load(cls, path): 1272 | cls.plutil_path = os.path.join(os.path.split(__file__)[0], 'plutil') 1273 | 1274 | if not os.path.isfile(XcodeProject.plutil_path): 1275 | cls.plutil_path = 'plutil' 1276 | 1277 | # load project by converting to xml and then convert that using plistlib 1278 | p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE) 1279 | stdout, stderr = p.communicate() 1280 | 1281 | # If the plist was malformed, returncode will be non-zero 1282 | if p.returncode != 0: 1283 | print stdout 1284 | return None 1285 | 1286 | tree = plistlib.readPlistFromString(stdout) 1287 | return XcodeProject(tree, path) 1288 | 1289 | 1290 | # The code below was adapted from plistlib.py. 1291 | 1292 | class PBXWriter(plistlib.PlistWriter): 1293 | def writeValue(self, value): 1294 | if isinstance(value, (PBXList, PBXDict)): 1295 | plistlib.PlistWriter.writeValue(self, value.data) 1296 | else: 1297 | plistlib.PlistWriter.writeValue(self, value) 1298 | 1299 | def simpleElement(self, element, value=None): 1300 | """ 1301 | We have to override this method to deal with Unicode text correctly. 1302 | Non-ascii characters have to get encoded as character references. 1303 | """ 1304 | if value is not None: 1305 | value = _escapeAndEncode(value) 1306 | self.writeln("<%s>%s" % (element, value, element)) 1307 | else: 1308 | self.writeln("<%s/>" % element) 1309 | 1310 | 1311 | # Regex to find any control chars, except for \t \n and \r 1312 | _controlCharPat = re.compile( 1313 | r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" 1314 | r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]") 1315 | 1316 | 1317 | def _escapeAndEncode(text): 1318 | m = _controlCharPat.search(text) 1319 | if m is not None: 1320 | raise ValueError("strings can't contains control characters; " 1321 | "use plistlib.Data instead") 1322 | text = text.replace("\r\n", "\n") # convert DOS line endings 1323 | text = text.replace("\r", "\n") # convert Mac line endings 1324 | text = text.replace("&", "&") # escape '&' 1325 | text = text.replace("<", "<") # escape '<' 1326 | text = text.replace(">", ">") # escape '>' 1327 | return text.encode("ascii", "xmlcharrefreplace") # encode as ascii with xml character references 1328 | -------------------------------------------------------------------------------- /unity/Assets/Countly/Editor/mod_pbxproj.py.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cc028b8c7bbf43d790b21a76b15afdb 3 | -------------------------------------------------------------------------------- /unity/Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26c15edc49556410b878ccfd2ec8f807 3 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60a4483f9e0724587b21435ae34ec3b2 3 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyBindings.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System; 26 | using System.Runtime.InteropServices; 27 | 28 | namespace Countly 29 | { 30 | public class Bindings : MonoBehaviour 31 | { 32 | #if !UNITY_EDITOR && UNITY_IPHONE 33 | [DllImport("__Internal")] 34 | private static extern string _CountlyGetAppVersion(); 35 | 36 | [DllImport("__Internal")] 37 | private static extern string _CountlyGetLocaleDescription(); 38 | 39 | [DllImport("__Internal")] 40 | private static extern string _CountlyGetCarrierName(); 41 | 42 | #elif !UNITY_EDITOR && UNITY_ANDROID 43 | 44 | private static AndroidJavaClass _countly = null; 45 | private static AndroidJavaClass _Countly 46 | { 47 | get 48 | { 49 | if (_countly == null) 50 | { 51 | _countly = 52 | new AndroidJavaClass("com.github.imkira.unitycountly.UnityCountly"); 53 | } 54 | return _countly; 55 | } 56 | } 57 | 58 | private static string _CountlyGetAppVersion() 59 | { 60 | return _Countly.CallStatic("getAppVersion"); 61 | } 62 | 63 | private static string _CountlyGetLocaleDescription() 64 | { 65 | return _Countly.CallStatic("getLocaleDescription"); 66 | } 67 | 68 | private static string _CountlyGetCarrierName() 69 | { 70 | return _Countly.CallStatic("getCarrierName"); 71 | } 72 | 73 | #else 74 | 75 | private static string _CountlyGetAppVersion() 76 | { 77 | return null; 78 | } 79 | 80 | private static string _CountlyGetLocaleDescription() 81 | { 82 | return null; 83 | } 84 | 85 | private static string _CountlyGetCarrierName() 86 | { 87 | return null; 88 | } 89 | 90 | #endif 91 | 92 | public static string GetAppVersion() 93 | { 94 | string version = _CountlyGetAppVersion(); 95 | 96 | if (string.IsNullOrEmpty(version) == true) 97 | { 98 | version = null; 99 | } 100 | 101 | return version; 102 | } 103 | 104 | public static string GetLocaleDescription() 105 | { 106 | string description = _CountlyGetLocaleDescription(); 107 | 108 | if (string.IsNullOrEmpty(description) == true) 109 | { 110 | description = Application.systemLanguage.ToString(); 111 | } 112 | 113 | return description; 114 | } 115 | 116 | public static string GetCarrierName() 117 | { 118 | string carrier = _CountlyGetCarrierName(); 119 | 120 | if (string.IsNullOrEmpty(carrier) == true) 121 | { 122 | carrier = null; 123 | } 124 | 125 | return carrier; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyBindings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4d377a5b918845d5b0d9120baeb5373 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyDatabase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System.IO; 26 | using System.Text; 27 | using System.Collections.Generic; 28 | 29 | namespace Countly 30 | { 31 | public class Queue 32 | { 33 | protected const string BASENAME = "Countly.dat"; 34 | protected string _filename; 35 | protected Queue _queue; 36 | protected int _maxCapacity; 37 | protected bool _useStorage; 38 | 39 | public int Count 40 | { 41 | get 42 | { 43 | return _queue.Count; 44 | } 45 | } 46 | 47 | public Queue(int capacity, int maxCapacity, bool useStorage) 48 | { 49 | _filename = Path.Combine(Application.persistentDataPath, BASENAME); 50 | _queue = new Queue(capacity); 51 | _maxCapacity = maxCapacity; 52 | _useStorage = useStorage; 53 | 54 | if (_useStorage == true) 55 | { 56 | EnqueueFromFile(); 57 | } 58 | } 59 | 60 | public string Peek() 61 | { 62 | return _queue.Peek(); 63 | } 64 | 65 | public void Enqueue(string data) 66 | { 67 | if (_queue.Count < _maxCapacity) 68 | { 69 | _queue.Enqueue(data); 70 | if (_useStorage == true) 71 | { 72 | AppendToFile(data); 73 | } 74 | } 75 | } 76 | 77 | public void Dequeue() 78 | { 79 | if (_queue.Count > 0) 80 | { 81 | _queue.Dequeue(); 82 | if (_useStorage == true) 83 | { 84 | SaveToFile(); 85 | } 86 | } 87 | } 88 | 89 | protected void EnqueueFromFile() 90 | { 91 | string[] lines = null; 92 | 93 | try 94 | { 95 | // read all lines from file 96 | lines = File.ReadAllLines(_filename, Encoding.UTF8); 97 | } 98 | catch (System.Exception) 99 | { 100 | lines = null; 101 | } 102 | 103 | // couldn't read file? 104 | if (lines == null) 105 | { 106 | return; 107 | } 108 | 109 | int enqueueCount = lines.Length; 110 | int newCount = enqueueCount + _queue.Count; 111 | 112 | // check capacity, check overflow 113 | if ((_queue.Count >= _maxCapacity) || 114 | (enqueueCount <= 0) || 115 | (newCount < enqueueCount) || 116 | (newCount < _queue.Count)) 117 | { 118 | return; 119 | } 120 | 121 | if (newCount > _maxCapacity) 122 | { 123 | // read last lines 124 | enqueueCount = _maxCapacity - _queue.Count; 125 | } 126 | 127 | string data; 128 | 129 | for (int i = 1; i <= enqueueCount; ++i) 130 | { 131 | data = lines[lines.Length - i]; 132 | _queue.Enqueue(data); 133 | } 134 | } 135 | 136 | protected void AppendToFile(string data) 137 | { 138 | try 139 | { 140 | using (StreamWriter file = new StreamWriter(_filename, true)) 141 | { 142 | file.WriteLine(data); 143 | } 144 | } 145 | catch (System.Exception) 146 | { 147 | } 148 | } 149 | 150 | public void SaveToFile() 151 | { 152 | if (_queue.Count > 0) 153 | { 154 | try 155 | { 156 | using (StreamWriter file = new StreamWriter(_filename)) 157 | { 158 | foreach (string data in _queue) 159 | { 160 | file.WriteLine(data); 161 | } 162 | } 163 | } 164 | catch (System.Exception) 165 | { 166 | } 167 | } 168 | else 169 | { 170 | DeleteFile(); 171 | } 172 | } 173 | 174 | public void DeleteFile() 175 | { 176 | try 177 | { 178 | File.Delete(_filename); 179 | } 180 | catch (System.Exception) 181 | { 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyDatabase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3974cba847fd34dccac9df2919d9ebf2 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyDeviceInfo.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System.Text; 26 | 27 | namespace Countly 28 | { 29 | public class DeviceInfo 30 | { 31 | public string UDID {get; set;} 32 | public string Device {get; set;} 33 | public string OSName {get; set;} 34 | public string OSVersion {get; set;} 35 | public string Carrier {get; set;} 36 | public string Resolution {get; set;} 37 | public string Locale {get; set;} 38 | public string AppVersion {get; set;} 39 | 40 | protected bool _isInitialized = false; 41 | 42 | public void Update() 43 | { 44 | if (_isInitialized == false) 45 | { 46 | _isInitialized = true; 47 | 48 | UDID = DetectUDID(); 49 | 50 | #if UNITY_EDITOR 51 | Device = "Unity Editor"; 52 | OSName = "Unity Editor"; 53 | #elif UNITY_IPHONE 54 | Device = iPhone.generation.ToString(); 55 | OSName = "iOS"; 56 | #elif UNITY_ANDROID 57 | Device = SystemInfo.deviceModel; 58 | OSName = "Android"; 59 | #elif UNITY_STANDALONE_OSX 60 | Device = "MAC"; 61 | OSName = "OS X"; 62 | #elif UNITY_STANDALONE_WIN 63 | Device = "PC"; 64 | OSName = "Windows"; 65 | #else 66 | Device = SystemInfo.deviceModel; 67 | OSName = "Unknown"; 68 | #endif 69 | 70 | OSVersion = SystemInfo.operatingSystem; 71 | AppVersion = Bindings.GetAppVersion(); 72 | } 73 | 74 | Carrier = Bindings.GetCarrierName(); 75 | Resolution = DetectResolution(); 76 | Locale = Bindings.GetLocaleDescription(); 77 | } 78 | 79 | public void JSONSerializeMetrics(StringBuilder builder) 80 | { 81 | // open metrics object 82 | builder.Append("{"); 83 | 84 | builder.Append("\"_device\":"); 85 | Utils.JSONSerializeString(builder, Device); 86 | 87 | builder.Append(",\"_os\":"); 88 | Utils.JSONSerializeString(builder, OSName); 89 | 90 | builder.Append(",\"_os_version\":"); 91 | Utils.JSONSerializeString(builder, OSVersion); 92 | 93 | if (Carrier != null) 94 | { 95 | builder.Append(",\"_carrier\":"); 96 | Utils.JSONSerializeString(builder, Carrier); 97 | } 98 | 99 | builder.Append(",\"_resolution\":"); 100 | Utils.JSONSerializeString(builder, Resolution); 101 | 102 | if (Locale != null) 103 | { 104 | builder.Append(",\"_locale\":"); 105 | Utils.JSONSerializeString(builder, Locale); 106 | } 107 | 108 | if (AppVersion != null) 109 | { 110 | builder.Append(",\"_app_version\":"); 111 | Utils.JSONSerializeString(builder, AppVersion); 112 | } 113 | 114 | // close metrics object 115 | builder.Append("}"); 116 | } 117 | 118 | public static string DetectUDID() 119 | { 120 | return SystemInfo.deviceUniqueIdentifier; 121 | } 122 | 123 | public static string DetectResolution() 124 | { 125 | Resolution resolution = Screen.currentResolution; 126 | return resolution.width + "x" + resolution.height; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyDeviceInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c4ebe1c5ebdc464bb1506ea9bc1aded 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyEvent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System.Collections.Generic; 26 | using System.Text; 27 | 28 | namespace Countly 29 | { 30 | public class Event 31 | { 32 | public string Key {get; set;} 33 | 34 | public long Count {get; set;} 35 | 36 | public long Timestamp {get; set;} 37 | 38 | protected bool _usesSum = false; 39 | public bool UsesSum 40 | { 41 | get 42 | { 43 | return _usesSum; 44 | } 45 | } 46 | 47 | protected double _sum; 48 | public double Sum 49 | { 50 | get 51 | { 52 | return _sum; 53 | } 54 | set 55 | { 56 | _usesSum = true; 57 | _sum = value; 58 | } 59 | } 60 | 61 | public Dictionary Segmentation {get; set;} 62 | 63 | public Event() 64 | { 65 | Timestamp = (long)Utils.GetCurrentTime(); 66 | } 67 | 68 | public void JSONSerialize(StringBuilder builder) 69 | { 70 | // open event object 71 | builder.Append("{"); 72 | 73 | builder.Append("\"key\":"); 74 | Utils.JSONSerializeString(builder, Key); 75 | 76 | builder.Append(",\"count\":"); 77 | builder.Append(Count); 78 | 79 | builder.Append(",\"timestamp\":"); 80 | builder.Append(Timestamp); 81 | 82 | if (UsesSum == true) 83 | { 84 | builder.Append(",\"sum\":"); 85 | Utils.JSONSerializeDouble(builder, Sum); 86 | } 87 | 88 | if ((Segmentation != null) && (Segmentation.Count > 0)) 89 | { 90 | builder.Append(",\"segmentation\":"); 91 | JSONSerializeSegmentation(builder); 92 | } 93 | 94 | // close event object 95 | builder.Append("}"); 96 | } 97 | 98 | protected void JSONSerializeSegmentation(StringBuilder builder) 99 | { 100 | bool first = true; 101 | 102 | // open segmentation object 103 | builder.Append("{"); 104 | 105 | foreach (KeyValuePair s in Segmentation) 106 | { 107 | if (first == true) 108 | { 109 | first = false; 110 | } 111 | else 112 | { 113 | builder.Append(","); 114 | } 115 | 116 | // serialize key 117 | Utils.JSONSerializeString(builder, s.Key); 118 | 119 | builder.Append(":"); 120 | 121 | // serialize value 122 | Utils.JSONSerializeString(builder, s.Value); 123 | } 124 | 125 | // close segmentation object 126 | builder.Append("}"); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: db9a1624e0c30416aab987f7f31492af 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyManager.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System.Collections; 26 | using System.Collections.Generic; 27 | using System.Text; 28 | 29 | namespace Countly 30 | { 31 | public class Manager : MonoBehaviour 32 | { 33 | public string appHost = "https://cloud.count.ly"; 34 | public string appKey; 35 | public bool allowDebug = false; 36 | public float updateInterval = 60f; 37 | public int eventSendThreshold = 10; 38 | public int queueLimit = 1024; 39 | public bool queueUsesStorage = true; 40 | 41 | public const string SDK_VERSION = "2.0"; 42 | 43 | protected DeviceInfo _deviceInfo = null; 44 | 45 | protected bool _isReady = false; 46 | protected bool _isRunning = false; 47 | protected bool _isSuspended = true; 48 | protected double _sessionLastSentAt = 0.0; 49 | protected double _unsentSessionLength = 0f; 50 | 51 | protected StringBuilder _connectionStringBuilder = null; 52 | protected bool _isProcessingConnection = false; 53 | protected Queue _connectionQueue = null; 54 | protected Queue ConnectionQueue 55 | { 56 | get 57 | { 58 | if (_connectionQueue == null) 59 | { 60 | _connectionQueue = new Queue(128, queueLimit, queueUsesStorage); 61 | } 62 | return _connectionQueue; 63 | } 64 | } 65 | 66 | protected StringBuilder _eventStringBuilder = null; 67 | protected List _eventQueue = null; 68 | protected List EventQueue 69 | { 70 | get 71 | { 72 | if (_eventQueue == null) 73 | { 74 | _eventQueue = new List(16); 75 | } 76 | return _eventQueue; 77 | } 78 | } 79 | 80 | public void Init(string appKey) 81 | { 82 | if (string.IsNullOrEmpty(appKey) == true) 83 | { 84 | return; 85 | } 86 | 87 | this.appKey = appKey; 88 | 89 | if ((_isRunning == true) || 90 | (_isReady == false)) 91 | { 92 | return; 93 | } 94 | 95 | Log("Initialize: " + appKey); 96 | 97 | _isRunning = true; 98 | Resume(); 99 | StartCoroutine(RunTimer()); 100 | } 101 | 102 | public void RecordEvent(Event e) 103 | { 104 | bool wasEmpty = (ConnectionQueue.Count <= 0); 105 | 106 | EventQueue.Add(e); 107 | FlushEvents(eventSendThreshold); 108 | 109 | if (wasEmpty == true) 110 | { 111 | ProcessConnectionQueue(); 112 | } 113 | } 114 | 115 | #region Unity Methods 116 | protected void Start() 117 | { 118 | _isReady = true; 119 | Init(appKey); 120 | } 121 | 122 | protected void OnApplicationPause(bool pause) 123 | { 124 | if (_isRunning == false) 125 | { 126 | return; 127 | } 128 | 129 | if (pause == true) 130 | { 131 | Log("OnApplicationPause -> Background"); 132 | Suspend(); 133 | } 134 | else 135 | { 136 | Log("OnApplicationPause -> Foreground"); 137 | Resume(); 138 | } 139 | } 140 | 141 | protected void OnApplicationQuit() 142 | { 143 | if (_isRunning == false) 144 | { 145 | return; 146 | } 147 | 148 | Log("OnApplicationQuit"); 149 | Suspend(); 150 | } 151 | #endregion 152 | 153 | #region Session Methods 154 | protected void BeginSession() 155 | { 156 | DeviceInfo info = GetDeviceInfo(); 157 | StringBuilder builder = InitConnectionDataStringBuilder(); 158 | 159 | // compute metrics 160 | info.JSONSerializeMetrics(builder); 161 | string metricsString = builder.ToString(); 162 | 163 | builder = InitConnectionData(info); 164 | 165 | builder.Append("&sdk_version="); 166 | AppendConnectionData(builder, SDK_VERSION); 167 | 168 | builder.Append("&begin_session=1"); 169 | 170 | builder.Append("&metrics="); 171 | AppendConnectionData(builder, metricsString); 172 | 173 | ConnectionQueue.Enqueue(builder.ToString()); 174 | ProcessConnectionQueue(); 175 | } 176 | 177 | protected void UpdateSession(long duration) 178 | { 179 | DeviceInfo info = GetDeviceInfo(); 180 | StringBuilder builder = InitConnectionData(info); 181 | 182 | builder.Append("&session_duration="); 183 | AppendConnectionData(builder, duration.ToString()); 184 | 185 | ConnectionQueue.Enqueue(builder.ToString()); 186 | ProcessConnectionQueue(); 187 | } 188 | 189 | protected void EndSession(long duration) 190 | { 191 | DeviceInfo info = GetDeviceInfo(); 192 | StringBuilder builder = InitConnectionData(info); 193 | 194 | builder.Append("&end_session=1"); 195 | 196 | builder.Append("&session_duration="); 197 | AppendConnectionData(builder, duration.ToString()); 198 | 199 | ConnectionQueue.Enqueue(builder.ToString()); 200 | ProcessConnectionQueue(); 201 | } 202 | 203 | protected void RecordEvents(List events) 204 | { 205 | DeviceInfo info = GetDeviceInfo(); 206 | StringBuilder builder = InitConnectionData(info); 207 | 208 | builder.Append("&events="); 209 | string eventsString = JSONSerializeEvents(events); 210 | AppendConnectionData(builder, eventsString); 211 | 212 | ConnectionQueue.Enqueue(builder.ToString()); 213 | } 214 | 215 | #endregion 216 | 217 | protected IEnumerator RunTimer() 218 | { 219 | while (true) 220 | { 221 | yield return new WaitForSeconds(updateInterval); 222 | 223 | if (_isSuspended == true) 224 | { 225 | continue; 226 | } 227 | 228 | // device info may have changed 229 | UpdateDeviceInfo(); 230 | 231 | // record any pending events 232 | FlushEvents(0); 233 | 234 | long duration = TrackSessionLength(); 235 | UpdateSession(duration); 236 | } 237 | } 238 | 239 | protected void Resume() 240 | { 241 | // already in unsuspeded state? 242 | if (_isSuspended == false) 243 | { 244 | return; 245 | } 246 | 247 | Log("Resuming..."); 248 | 249 | _isSuspended = false; 250 | _sessionLastSentAt = Utils.GetCurrentTime(); 251 | 252 | // device info may have changed 253 | UpdateDeviceInfo(); 254 | 255 | BeginSession(); 256 | } 257 | 258 | protected void Suspend() 259 | { 260 | // already in suspended state? 261 | if (_isSuspended == true) 262 | { 263 | return; 264 | } 265 | 266 | Log("Suspending..."); 267 | 268 | _isSuspended = true; 269 | 270 | // device info may have changed 271 | UpdateDeviceInfo(); 272 | 273 | // record any pending events 274 | FlushEvents(0); 275 | 276 | long duration = TrackSessionLength(); 277 | EndSession(duration); 278 | } 279 | 280 | #region Utility Methods 281 | protected void ProcessConnectionQueue() 282 | { 283 | if ((_isProcessingConnection == true) || 284 | (ConnectionQueue.Count <= 0)) 285 | { 286 | return; 287 | } 288 | 289 | _isProcessingConnection = true; 290 | StartCoroutine(_ProcessConnectionQueue()); 291 | } 292 | 293 | protected IEnumerator _ProcessConnectionQueue() 294 | { 295 | while (ConnectionQueue.Count > 0) 296 | { 297 | string data = ConnectionQueue.Peek(); 298 | string urlString = appHost + "/i?" + data; 299 | 300 | Log("Request started: " + urlString); 301 | 302 | WWW www = new WWW(urlString) 303 | { 304 | threadPriority = ThreadPriority.Low 305 | }; 306 | 307 | yield return www; 308 | 309 | if (string.IsNullOrEmpty(www.error) == false) 310 | { 311 | Log("Request failed: " + www.error); 312 | break; 313 | } 314 | 315 | ConnectionQueue.Dequeue(); 316 | Log("Request completed"); 317 | } 318 | 319 | _isProcessingConnection = false; 320 | } 321 | 322 | protected DeviceInfo GetDeviceInfo() 323 | { 324 | if (_deviceInfo == null) 325 | { 326 | _deviceInfo = new DeviceInfo(); 327 | } 328 | return _deviceInfo; 329 | } 330 | 331 | protected DeviceInfo UpdateDeviceInfo() 332 | { 333 | DeviceInfo info = GetDeviceInfo(); 334 | info.Update(); 335 | return info; 336 | } 337 | 338 | protected void FlushEvents(int threshold) 339 | { 340 | List eventQueue = EventQueue; 341 | 342 | // satisfy minimum number of eventQueue 343 | if ((eventQueue.Count <= 0) || 344 | (eventQueue.Count < threshold)) 345 | { 346 | return; 347 | } 348 | 349 | RecordEvents(eventQueue); 350 | eventQueue.Clear(); 351 | } 352 | 353 | protected long TrackSessionLength() 354 | { 355 | double now = Utils.GetCurrentTime(); 356 | 357 | if (now > _sessionLastSentAt) 358 | { 359 | _unsentSessionLength += now - _sessionLastSentAt; 360 | } 361 | 362 | // duration should be integer 363 | long duration = (long)_unsentSessionLength; 364 | 365 | // sanity check 366 | if (duration < 0) 367 | { 368 | duration = 0; 369 | } 370 | 371 | // keep decimal part 372 | _unsentSessionLength -= duration; 373 | 374 | return duration; 375 | } 376 | 377 | protected StringBuilder InitConnectionDataStringBuilder() 378 | { 379 | if (_connectionStringBuilder == null) 380 | { 381 | _connectionStringBuilder = new StringBuilder(1024); 382 | } 383 | else 384 | { 385 | _connectionStringBuilder.Length = 0; 386 | } 387 | 388 | return _connectionStringBuilder; 389 | } 390 | 391 | protected StringBuilder InitConnectionData(DeviceInfo info) 392 | { 393 | StringBuilder builder = InitConnectionDataStringBuilder(); 394 | 395 | builder.Append("app_key="); 396 | AppendConnectionData(builder, appKey); 397 | 398 | builder.Append("&device_id="); 399 | AppendConnectionData(builder, info.UDID); 400 | 401 | builder.Append("×tamp="); 402 | 403 | long timestamp = (long)Utils.GetCurrentTime(); 404 | builder.Append(timestamp); 405 | 406 | return builder; 407 | } 408 | 409 | protected void AppendConnectionData(StringBuilder builder, string val) 410 | { 411 | if (string.IsNullOrEmpty(val) != true) 412 | { 413 | builder.Append(Utils.EscapeURL(val)); 414 | } 415 | } 416 | 417 | protected StringBuilder InitEventStringBuilder() 418 | { 419 | if (_eventStringBuilder == null) 420 | { 421 | _eventStringBuilder = new StringBuilder(1024); 422 | } 423 | else 424 | { 425 | _eventStringBuilder.Length = 0; 426 | } 427 | 428 | return _eventStringBuilder; 429 | } 430 | 431 | protected string JSONSerializeEvents(List events) 432 | { 433 | StringBuilder builder = InitEventStringBuilder(); 434 | 435 | // open array of events 436 | builder.Append("["); 437 | 438 | bool first = true; 439 | 440 | foreach (Event e in events) 441 | { 442 | if (first == true) 443 | { 444 | first = false; 445 | } 446 | else 447 | { 448 | builder.Append(","); 449 | } 450 | 451 | e.JSONSerialize(builder); 452 | } 453 | 454 | // close array of events 455 | builder.Append("]"); 456 | 457 | return builder.ToString(); 458 | } 459 | 460 | protected void Log(string str) 461 | { 462 | if (allowDebug == true) 463 | { 464 | Debug.Log(str); 465 | } 466 | } 467 | #endregion 468 | } 469 | } 470 | 471 | public class CountlyManager : Countly.Manager 472 | { 473 | protected static Countly.Manager _instance = null; 474 | public static Countly.Manager Instance 475 | { 476 | get 477 | { 478 | if (_instance == null) 479 | { 480 | GameObject singleton = GameObject.Find("CountlyManager"); 481 | 482 | if (singleton != null) 483 | { 484 | _instance = singleton.GetComponent(); 485 | } 486 | } 487 | 488 | return _instance; 489 | } 490 | } 491 | 492 | protected void Awake() 493 | { 494 | if ((_instance != this) && (_instance != null)) 495 | { 496 | Log("Duplicate manager detected. Destroying..."); 497 | Destroy(gameObject); 498 | return; 499 | } 500 | 501 | _instance = this; 502 | DontDestroyOnLoad(gameObject); 503 | } 504 | 505 | public static new void Init(string appKey = null) 506 | { 507 | Countly.Manager instance = Instance; 508 | 509 | if (instance != null) 510 | { 511 | if (appKey == null) 512 | { 513 | appKey = instance.appKey; 514 | } 515 | instance.Init(appKey); 516 | } 517 | } 518 | 519 | public static void Emit(string key, long count) 520 | { 521 | Countly.Manager instance = Instance; 522 | 523 | if (instance != null) 524 | { 525 | Countly.Event e = new Countly.Event(); 526 | 527 | e.Key = key; 528 | e.Count = count; 529 | 530 | instance.RecordEvent(e); 531 | } 532 | } 533 | 534 | public static void Emit(string key, long count, double sum) 535 | { 536 | Countly.Manager instance = Instance; 537 | 538 | if (instance != null) 539 | { 540 | Countly.Event e = new Countly.Event(); 541 | 542 | e.Key = key; 543 | e.Count = count; 544 | e.Sum = sum; 545 | 546 | instance.RecordEvent(e); 547 | } 548 | } 549 | 550 | public static void Emit(string key, long count, 551 | Dictionary segmentation) 552 | { 553 | Countly.Manager instance = Instance; 554 | 555 | if (instance != null) 556 | { 557 | Countly.Event e = new Countly.Event(); 558 | 559 | e.Key = key; 560 | e.Count = count; 561 | e.Segmentation = segmentation; 562 | 563 | instance.RecordEvent(e); 564 | } 565 | } 566 | 567 | public static void Emit(string key, long count, double sum, 568 | Dictionary segmentation) 569 | { 570 | Countly.Manager instance = Instance; 571 | 572 | if (instance != null) 573 | { 574 | Countly.Event e = new Countly.Event(); 575 | 576 | e.Key = key; 577 | e.Count = count; 578 | e.Sum = sum; 579 | e.Segmentation = segmentation; 580 | 581 | instance.RecordEvent(e); 582 | } 583 | } 584 | 585 | public static void Emit(Countly.Event e) 586 | { 587 | Countly.Manager instance = Instance; 588 | 589 | if (instance != null) 590 | { 591 | instance.RecordEvent(e); 592 | } 593 | } 594 | } 595 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 316f633e5574640dca547070fbe4ccdc 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyUtils.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | using UnityEngine; 25 | using System; 26 | using System.Text; 27 | 28 | namespace Countly 29 | { 30 | public class Utils 31 | { 32 | protected static DateTime EPOCH_TIME = 33 | new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 34 | 35 | public static double GetCurrentTime() 36 | { 37 | TimeSpan t = (DateTime.UtcNow - EPOCH_TIME); 38 | return t.TotalSeconds; 39 | } 40 | 41 | public static string EscapeURL(string str) 42 | { 43 | return WWW.EscapeURL(str); 44 | } 45 | 46 | public static void JSONSerializeDouble(StringBuilder builder, double val) 47 | { 48 | builder.Append(val.ToString("R")); 49 | } 50 | 51 | public static void JSONSerializeString(StringBuilder builder, string str) 52 | { 53 | if (str == null) 54 | { 55 | builder.Append("null"); 56 | return; 57 | } 58 | 59 | char c; 60 | int length = str.Length; 61 | 62 | builder.Append('\"'); 63 | 64 | for (int i = 0; i < length; i++) 65 | { 66 | c = str[i]; 67 | 68 | switch (c) 69 | { 70 | case '\b': 71 | builder.Append("\\b"); 72 | break; 73 | 74 | case '\t': 75 | builder.Append("\\t"); 76 | break; 77 | 78 | case '\n': 79 | builder.Append("\\n"); 80 | break; 81 | 82 | case '\f': 83 | builder.Append("\\f"); 84 | break; 85 | 86 | case '\r': 87 | builder.Append("\\r"); 88 | break; 89 | 90 | case '"': 91 | builder.Append("\\\""); 92 | break; 93 | 94 | case '\\': 95 | builder.Append("\\\\"); 96 | break; 97 | 98 | default: 99 | int cp = System.Convert.ToInt32(c); 100 | 101 | if ((cp >= 32) && (cp <= 126)) 102 | { 103 | builder.Append(c); 104 | } 105 | else 106 | { 107 | builder.Append("\\u"); 108 | builder.Append(cp.ToString("x4")); 109 | } 110 | break; 111 | } 112 | } 113 | 114 | builder.Append('\"'); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/Countly/CountlyUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f4844511465d4cc898c572ed051278f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/iOS.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f18f10555d2c40c9bb064ff8054f399 3 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/iOS/UnityCountly.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #import 25 | #import 26 | #import 27 | 28 | #define NSSTRING_TO_STR_OR_EMPTY(__t__) \ 29 | (((__t__) == nil) ? NULL : strdup([(__t__) UTF8String])) 30 | 31 | extern "C" 32 | { 33 | char* _CountlyGetAppVersion(); 34 | char* _CountlyGetLocaleDescription(); 35 | char* _CountlyGetCarrierName(); 36 | } 37 | 38 | char* _CountlyGetAppVersion() 39 | { 40 | NSString *version = [[NSBundle mainBundle] 41 | objectForInfoDictionaryKey:(NSString*)kCFBundleVersionKey]; 42 | return NSSTRING_TO_STR_OR_EMPTY(version); 43 | } 44 | 45 | char* _CountlyGetLocaleDescription() 46 | { 47 | NSString* localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; 48 | return NSSTRING_TO_STR_OR_EMPTY(localeIdentifier); 49 | } 50 | 51 | char* _CountlyGetCarrierName() 52 | { 53 | char* carrierName = NULL; 54 | 55 | CTTelephonyNetworkInfo* netinfo = [[CTTelephonyNetworkInfo alloc] init]; 56 | 57 | if (netinfo != nil) 58 | { 59 | CTCarrier* carrier = [netinfo subscriberCellularProvider]; 60 | 61 | if (carrier != nil) 62 | { 63 | carrierName = NSSTRING_TO_STR_OR_EMPTY([carrier carrierName]); 64 | } 65 | 66 | [netinfo release]; 67 | } 68 | 69 | return carrierName; 70 | } 71 | -------------------------------------------------------------------------------- /unity/Assets/Plugins/iOS/UnityCountly.mm.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b1a4cb4cf30d4a2eb861a737ce6ce92 3 | -------------------------------------------------------------------------------- /unity/ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1045 &1 4 | EditorBuildSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Scenes: 8 | - enabled: 1 9 | path: Assets/Countly/Demo/unity-countly-demo.unity 10 | -------------------------------------------------------------------------------- /unity/ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!159 &1 4 | EditorSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 1 7 | m_ExternalVersionControlSupport: 1 8 | m_SerializationMode: 2 9 | m_WebSecurityEmulationEnabled: 0 10 | m_WebSecurityEmulationHostUrl: http://www.mydomain.com/mygame.unity3d 11 | -------------------------------------------------------------------------------- /unity/ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/NavMeshLayers.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/NavMeshLayers.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!129 &1 4 | PlayerSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | AndroidProfiler: 0 8 | defaultScreenOrientation: 0 9 | targetDevice: 2 10 | targetPlatform: 1 11 | targetResolution: 0 12 | accelerometerFrequency: 60 13 | companyName: imkira 14 | productName: unity-countly 15 | AndroidLicensePublicKey: 16 | defaultScreenWidth: 1024 17 | defaultScreenHeight: 768 18 | defaultScreenWidthWeb: 960 19 | defaultScreenHeightWeb: 600 20 | m_RenderingPath: 1 21 | m_ActiveColorSpace: 0 22 | m_MTRendering: 1 23 | iosShowActivityIndicatorOnLoading: -1 24 | androidShowActivityIndicatorOnLoading: -1 25 | displayResolutionDialog: 1 26 | allowedAutorotateToPortrait: 1 27 | allowedAutorotateToPortraitUpsideDown: 1 28 | allowedAutorotateToLandscapeRight: 1 29 | allowedAutorotateToLandscapeLeft: 1 30 | useOSAutorotation: 1 31 | use32BitDisplayBuffer: 0 32 | use24BitDepthBuffer: 0 33 | defaultIsFullScreen: 0 34 | runInBackground: 0 35 | captureSingleScreen: 0 36 | Override IPod Music: 0 37 | usePlayerLog: 1 38 | useMacAppStoreValidation: 0 39 | xboxSkinOnGPU: 1 40 | xboxEnableAvatar: 0 41 | xboxEnableKinect: 0 42 | xboxEnableKinectAutoTracking: 0 43 | xboxEnableSpeech: 0 44 | wiiHio2Usage: -1 45 | wiiLoadingScreenRectPlacement: 0 46 | wiiLoadingScreenBackground: {r: 1, g: 1, b: 1, a: 1} 47 | wiiLoadingScreenPeriod: 1000 48 | wiiLoadingScreenFileName: 49 | wiiLoadingScreenRect: 50 | serializedVersion: 2 51 | x: 0 52 | y: 0 53 | width: 0 54 | height: 0 55 | m_SupportedAspectRatios: 56 | 4:3: 1 57 | 5:4: 1 58 | 16:10: 1 59 | 16:9: 1 60 | Others: 1 61 | debugUnloadMode: 0 62 | iPhoneBundleIdentifier: com.github.imkira.unitycountly 63 | iPhoneBundleVersion: 1.0 64 | AndroidBundleVersionCode: 1 65 | AndroidMinSdkVersion: 6 66 | AndroidPreferredInstallLocation: 1 67 | aotOptions: 68 | apiCompatibilityLevel: 2 69 | iPhoneStrippingLevel: 0 70 | iPhoneScriptCallOptimization: 0 71 | ForceInternetPermission: 0 72 | ForceSDCardPermission: 0 73 | CreateWallpaper: 0 74 | StripUnusedMeshComponents: 0 75 | iPhoneSdkVersion: 988 76 | iPhoneTargetOSVersion: 7 77 | uIPrerenderedIcon: 0 78 | uIRequiresPersistentWiFi: 0 79 | uIStatusBarHidden: 1 80 | uIExitOnSuspend: 0 81 | uIStatusBarStyle: 0 82 | iPhoneSplashScreen: {fileID: 0} 83 | iPhoneHighResSplashScreen: {fileID: 0} 84 | iPhoneTallHighResSplashScreen: {fileID: 0} 85 | iPadPortraitSplashScreen: {fileID: 0} 86 | iPadHighResPortraitSplashScreen: {fileID: 0} 87 | iPadLandscapeSplashScreen: {fileID: 0} 88 | iPadHighResLandscapeSplashScreen: {fileID: 0} 89 | AndroidTargetDevice: 0 90 | AndroidTargetGraphics: 1 91 | AndroidSplashScreenScale: 0 92 | AndroidKeystoreName: 93 | AndroidKeyaliasName: 94 | resolutionDialogBanner: {fileID: 0} 95 | m_BuildTargetIcons: 96 | - m_BuildTarget: 97 | m_Icons: 98 | - m_Icon: {fileID: 0} 99 | m_Size: 128 100 | m_BuildTargetBatching: [] 101 | webPlayerTemplate: APPLICATION:Default 102 | m_TemplateCustomTags: {} 103 | wiiRegion: 1 104 | wiiGameCode: RABA 105 | wiiGameVersion: 106 | wiiCompanyCode: ZZ 107 | wiiSupportsNunchuk: 0 108 | wiiSupportsClassicController: 0 109 | wiiSupportsBalanceBoard: 0 110 | wiiSupportsMotionPlus: 0 111 | wiiControllerCount: 1 112 | wiiFloatingPointExceptions: 0 113 | wiiScreenCrashDumps: 1 114 | wiiMemoryLabelCount: 147 115 | wiiMemorySetup: 0000000000000000000000000000000000000000 116 | XboxTitleId: 117 | XboxImageXexPath: 118 | XboxSpaPath: 119 | XboxGenerateSpa: 0 120 | XboxDeployKinectResources: 0 121 | XboxSplashScreen: {fileID: 0} 122 | xboxSpeechDB: 0 123 | ps3TitleConfigPath: 124 | ps3DLCConfigPath: 125 | ps3ThumbnailPath: 126 | ps3BackgroundPath: 127 | ps3SoundPath: 128 | ps3TrophyCommId: 129 | ps3TrophyPackagePath: 130 | ps3ReservedMemorySizeMB: 2 131 | ps3BootCheckMaxSaveGameSizeKB: 128 132 | ps3TrophyCommSig: 133 | ps3SaveGameSlots: 1 134 | ps3EDATKeyFilePath: 135 | ps3TrialMode: 0 136 | flashStrippingLevel: 2 137 | firstStreamedLevelWithResources: 0 138 | unityRebuildLibraryVersion: 9 139 | unityForwardCompatibleVersion: 36 140 | unityStandardAssetsVersion: 0 141 | -------------------------------------------------------------------------------- /unity/ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /unity/ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkira/unity-countly/fde24d793a7c31d4bb3cb348def452274e68fd80/unity/ProjectSettings/TimeManager.asset --------------------------------------------------------------------------------